00 OTA neden zor
Sahadaki gömülü cihazları güncellemek, masaüstü yazılım güncellemesinden çok daha risklidir; güç kesintisi, bozuk paket veya bağlantı kopması cihazı kalıcı olarak kullanılamaz hale getirebilir.
Güç kesintisi riski
Gömülü sistemlerde güncelleme sırasında güç kesilebilir. Eğer update, rootfs partition'ına yarım yazılmışsa cihaz boot edemez. Fiziksel erişim yoksa brick olmuş bir cihazı düzeltmek ya çok maliyetlidir ya da imkânsızdır. Bu yüzden atomicity — ya tümü ya hiçbiri garantisi — OTA'nın birinci şartıdır.
Rollback zorunluluğu
Yeni firmware'in bir donanım revizyonuyla uyumsuz olduğu, regresyon içerdiği veya network stack'inin bozuk geldiği durumlar yaşanır. Bu senaryolarda sistem otomatik olarak eski sürüme geri dönebilmelidir. İnsan müdahalesi gerektiren rollback, production ortamında kabul edilemez.
OTA araçları karşılaştırması
| Araç | Dil | A/B | İmzalama | Server | Uygun senaryo |
|---|---|---|---|---|---|
| SWUpdate | C | Evet | RSA/CMS | HawkBit, özel | Küçük-orta, esnek handler |
| Mender | C++/Go | Evet | RSA | SaaS/self-hosted | Kurumsal, yönetilen fleet |
| RAUC | C | Evet | CMS | Hawk-Bit, hawkBit | systemd ekosistemi |
| OSTree | C | Evet | GPG | Flatpak sunucusu | Desktop-embedded hybrid |
| swupdate (custom) | Bash/Python | Manuel | Yok | Yok | Prototip, geliştirme |
Seçim kriterleri
Bu bölümde
- OTA'nın temel zorluğu: atomicity, rollback, güvenlik
- SWUpdate, Mender, RAUC, OSTree karşılaştırması
- Seçim kriterleri: cihaz sayısı, build sistemi, delta ihtiyacı
01 A/B partition şeması
A/B (dual-bank) partition şeması, güncellemenin pasif slota yazılmasını, onaylanana kadar aktif slotan boot edilmesini sağlayan en güvenilir OTA mimarisidir.
Partition düzeni
eMMC ┌───────────────────────────────────────────────────────┐ │ p1: boot 64 MB FAT32 kernel + dtb + initramfs │ │ p2: rootfsA 256 MB squashfs Slot A ← aktif (v1.0) │ │ p3: rootfsB 256 MB squashfs Slot B ← güncelleme hedefi │ │ p4: data Kalan ext4 Kalıcı veri + config │ └───────────────────────────────────────────────────────┘
Güncelleme akışı
[1] OTA daemon yeni rootfs.squashfs indirir → /data/tmp/
[2] Slot B'ye (p3) yazar: dd if=rootfs-v1.1.squashfs of=/dev/mmcblk0p3
[3] U-Boot env: boot_slot=B, upgrade_available=1, bootcount=0
[4] Reboot
[5] U-Boot Slot B'den boot eder
[6] Health check servisi (30s): servisler OK?
→ Evet: fw_setenv upgrade_available 0 (B kalıcı)
→ Hayır: reboot
[7] bootcount > 3: U-Boot Slot A'ya döner (rollback)
U-Boot environment değişkenleri
# Mevcut ortamı görüntüle
fw_printenv
# boot_slot=A
# upgrade_available=0
# bootcount=0
# bootlimit=3
# Güncelleme öncesi: Slot B'yi hedefle
fw_setenv boot_slot B
fw_setenv upgrade_available 1
fw_setenv bootcount 0
# Güncelleme onayı (health check başarılı)
fw_setenv upgrade_available 0
# Mevcut aktif slotu sor
fw_printenv boot_slot | cut -d= -f2
U-Boot bootscript
if test "${upgrade_available}" = "1"; then
setexpr bootcount ${bootcount} + 1
saveenv
if test ${bootcount} > ${bootlimit}; then
echo "Rollback! bootcount=${bootcount}"
setenv boot_slot A
setenv upgrade_available 0
setenv bootcount 0
saveenv
fi
fi
if test "${boot_slot}" = "B"; then
setenv mmcpart 3
else
setenv mmcpart 2
fi
setenv bootargs console=ttymxc0,115200 \
root=/dev/mmcblk0p${mmcpart} rootfstype=squashfs ro \
systemd.volatile=state quiet
sqfsload mmc 0:${mmcpart} ${loadaddr} /boot/Image
sqfsload mmc 0:${mmcpart} ${fdt_addr_r} /boot/myboard.dtb
booti ${loadaddr} - ${fdt_addr_r}
Bu bölümde
- A/B partition şeması: boot + rootfsA + rootfsB + data
- Güncelleme akışı: indir → pasif slota yaz → reboot → health check → onayla
fw_setenv/fw_printenv: U-Boot env değişkenleri- U-Boot bootscript: bootcount + rollback mantığı
02 SWUpdate temelleri
SWUpdate, gömülü Linux için C ile yazılmış esnek bir güncelleme framework'üdür; CPIO tabanlı .swu formatı, pluggable handler'lar ve web arayüzü içerir.
SWUpdate mimarisi
.swu dosyası (CPIO arşivi) ├── sw-description (libconfig formatında güncelleme tarifi) ├── sw-description.sig (opsiyonel imza) ├── rootfs.squashfs (rootfs image) ├── kernel.itb (kernel FIT image) └── postinstall.sh (opsiyonel script) SWUpdate süreci: [parse sw-description] → [doğrula] → [handler'ları çağır] → [bootloader env güncelle]
Build ve konfigürasyon
# Kaynak koddan derleme
git clone https://github.com/sbabic/swupdate.git
cd swupdate
# Handler seçimi (menuconfig)
make menuconfig
# Handlers → Raw image handler (rawfile)
# Handlers → Shell script handler
# Bootloader support → U-Boot support
# Network → Embedded web server (mongoose)
make -j$(nproc)
# Cross-compile
make CROSS_COMPILE=arm-linux-gnueabihf- \
ARCH=arm \
-j$(nproc)
Temel çalışma modları
swupdate -i update.swu — dosyadan okuma.cat update.swu | swupdate — pipe'dan okuma.swupdate -u "-t default -u http://hawkbit:8080" — HawkBit server polling.swupdate -w "-document_root /var/www/swupdate" — Tarayıcıdan .swu yükleme.swupdate -d "-url http://server/update.swu" — URL'den indir ve uygula.Handler türleri
| Handler | Açıklama | Kullanım |
|---|---|---|
| raw | Block device'a ham yazma | rootfs, kernel partition |
| rawfile | Dosya sistemi üzerine dosya yaz | tek dosya güncelleme |
| shellscript | Bash script çalıştır | pre/post install işlemler |
| ubivol | UBI volume'a yaz | Raw NAND flash |
| archive | tar.gz'yi dosya sistemine aç | /data güncelleme |
| bootloader | U-Boot environment güncelle | Slot geçişi |
Bu bölümde
- .swu formatı: CPIO arşivi içinde sw-description + image dosyaları
- Çalışma modları: local, suricatta (HawkBit), web server, HTTP download
- Handler türleri: raw, rawfile, shellscript, ubivol, bootloader
- Build:
make menuconfigile handler seçimi
03 sw-description yazımı
sw-description, .swu paketinin tarifidir; libconfig formatında yazılır ve hangi dosyanın nereye nasıl yükleneceğini belirtir.
Temel yapı
software =
{
version = "1.1.0";
description = "MyBoard firmware v1.1.0";
hardware-compatibility = ["1.0", "1.1", "2.0"];
images: (
{
filename = "rootfs.squashfs";
type = "raw";
device = "/dev/mmcblk0p3"; /* Slot B */
sha256 = "abc123...def456";
},
{
filename = "kernel.itb";
type = "raw";
device = "/dev/mmcblk0p1";
sha256 = "fed321...cba654";
offset = "0x100000"; /* 1 MB offset */
}
);
scripts: (
{
filename = "postinstall.sh";
type = "shellscript";
}
);
bootenv: (
{
name = "boot_slot";
value = "B";
},
{
name = "upgrade_available";
value = "1";
},
{
name = "bootcount";
value = "0";
}
);
};
Multi-image güncelleme
software =
{
version = "2.0.0";
hardware-compatibility = ["2.0"];
images: (
{
filename = "rootfs-v2.squashfs";
type = "raw";
device = "/dev/mmcblk0p3";
sha256 = "11223344...";
compressed = false; /* squashfs zaten sıkıştırılmış */
},
{
filename = "Image";
type = "rawfile";
path = "/boot/Image";
sha256 = "aabbccdd...";
},
{
filename = "myboard.dtb";
type = "rawfile";
path = "/boot/myboard.dtb";
sha256 = "11aabb22...";
}
);
scripts: (
{
filename = "pre-install.sh";
type = "shellscript";
properties: { "install-if-different" = ["true"]; };
}
);
};
Şifreli image
images: (
{
filename = "rootfs.squashfs.enc";
type = "raw";
device = "/dev/mmcblk0p3";
encrypted = true;
sha256 = "...";
}
);
# SHA256 hesapla
SHA_ROOTFS=$(sha256sum rootfs.squashfs | cut -d ' ' -f1)
SHA_KERNEL=$(sha256sum Image | cut -d ' ' -f1)
# sw-description içine hash'leri yerleştir
sed -i "s/ROOTFS_SHA256/$SHA_ROOTFS/g" sw-description
sed -i "s/KERNEL_SHA256/$SHA_KERNEL/g" sw-description
# CPIO arşivi oluştur (sw-description MUTLAKA ilk sırada)
echo sw-description sw-description.sig rootfs.squashfs Image myboard.dtb postinstall.sh \
| tr ' ' '\n' \
| cpio -ovH newc > firmware-v1.1.0.swu
sw-description CPIO arşivinde ilk dosya olmalıdır. SWUpdate, arşivi stream modunda işler ve ilk önce sw-description'ı parse etmesi gerekir. Sıralama yanlışsa swupdate: sw-description not found hatası alırsınız.
Bu bölümde
- sw-description libconfig formatı:
software,images,scripts,bootenv - hardware-compatibility: donanım revizyonu kontrolü
- sha256 doğrulaması: her image için hash zorunlu
- Şifreli image:
encrypted = true - CPIO paket oluşturma: sw-description ilk sırada olmalı
04 SWUpdate ile A/B güncelleme
SWUpdate'in bootloader handler'ı ve Lua/shell script desteğiyle tam A/B güncelleme pipeline'ı kurulabilir.
Aktif slotu tespit etme
#!/bin/sh
# SWUpdate, bu script'i images kopyalanmadan önce çalıştırır
# Aktif slotu oku, hedef slotu /tmp/target_slot'a yaz
ACTIVE=$(fw_printenv boot_slot 2>/dev/null | cut -d= -f2)
ACTIVE=${ACTIVE:-A}
if [ "$ACTIVE" = "A" ]; then
TARGET_SLOT="B"
TARGET_DEV="/dev/mmcblk0p3"
else
TARGET_SLOT="A"
TARGET_DEV="/dev/mmcblk0p2"
fi
echo "$TARGET_SLOT" > /tmp/swupdate_target_slot
echo "$TARGET_DEV" > /tmp/swupdate_target_dev
echo "[preinstall] Aktif: $ACTIVE, Hedef: $TARGET_SLOT ($TARGET_DEV)"
Dinamik device: Lua script ile
software =
{
version = "1.2.0";
hardware-compatibility = ["1.0"];
hardware: {
revision = "myboard-1.0";
};
images: (
{
filename = "rootfs.squashfs";
type = "raw";
lua_handler = "get_target_device"; /* Lua ile device seç */
sha256 = "...";
}
);
bootenv: (
{ name = "upgrade_available"; value = "1"; },
{ name = "bootcount"; value = "0"; }
);
};
İmzalı .swu oluşturma
# Private key ve self-signed cert oluştur
openssl req -x509 -newkey rsa:4096 -keyout private.pem \
-out public.pem -days 3650 -nodes \
-subj "/CN=SWUpdate Signing Key"
# sw-description'ı imzala (CMS / PKCS#7)
openssl cms -sign -in sw-description \
-out sw-description.sig \
-signer public.pem \
-inkey private.pem \
-outform DER \
-nosmimecap -binary
# İmzalı .swu paketle
echo sw-description sw-description.sig rootfs.squashfs \
| tr ' ' '\n' | cpio -ovH newc > firmware-signed.swu
# SWUpdate ile imzalı paketi uygula
swupdate -k public.pem -i firmware-signed.swu
Web arayüzü ile güncelleme
# Web server ile başlat (mongoose embedded)
swupdate -w "-document_root /var/www/swupdate -port 8080"
# Tarayıcıdan: http://device-ip:8080
# .swu dosyasını sürükle-bırak ile yükle
# Veya curl ile gönder
curl -F "swupdate=@firmware.swu" \
http://192.168.1.100:8080/upload
SWUpdate, güncelleme sırasında sistem sinyallerini yakalar. SIGINT veya SIGTERM alırsa güncellemeyi tamamlamadan çıkmaz — mevcut image yazma işlemi tamamlanır. Bu, kısmi yazmanın önüne geçmez; atomik güncelleme garantisi yalnızca A/B slotlarla sağlanır.
Bu bölümde
- preinstall.sh: aktif slot tespiti ve hedef device belirleme
- RSA/CMS imzalama:
openssl cms -signile sw-description imzası swupdate -k public.pem -i signed.swuile imzalı güncelleme- Web arayüzü: mongoose ile tarayıcıdan güncelleme
curl -F swupdate=@firmware.swuile programatik güncelleme
05 Mender temelleri
Mender, merkezi yönetim sunucusu, signed artifact formatı ve tam A/B güncelleme desteğiyle kurumsal ölçekli IoT filolarında tercih edilen OTA platformudur.
Mender mimarisi
Mender Server (SaaS / self-hosted)
├── Device Management API
├── Deployment Service
└── Artifact Repository
│ HTTPS polling (30s-24h)
▼
Mender Client (C++ daemon, cihazda)
├── Inventory
├── Update Manager
└── State Machine
│
▼
Rootfs (A/B swap via mender-grub / U-Boot)
Kurulum
# Mender client kurulumu (binary)
wget https://downloads.mender.io/mender-client/latest/linux/arm/mender
chmod +x mender
mv mender /usr/bin/
# mender-artifact CLI kurulumu (host makinede)
wget https://downloads.mender.io/mender-artifact/latest/linux/mender-artifact
chmod +x mender-artifact
mv mender-artifact /usr/local/bin/
Mender konfigürasyonu
{
"ServerURL": "https://hosted.mender.io",
"TenantToken": "eyJhbGciOi...",
"DeviceTypeFile": "/var/lib/mender/device_type",
"UpdatePollIntervalSeconds": 1800,
"InventoryPollIntervalSeconds": 28800,
"RetryPollIntervalSeconds": 300,
"RootfsPartA": "/dev/mmcblk0p2",
"RootfsPartB": "/dev/mmcblk0p3",
"BootUtilitiesGetNextActivePart": "mender-grubenv-get-next-active",
"BootUtilitiesSetActivePart": "mender-grubenv-set-active"
}
Device type dosyası
device_type=raspberrypi4
Mender state machine
Idle → Checking → Downloading → Installing → Rebooting
│
┌────────────────────────┘
▼
ArtifactVerifyReboot
│
OK ───────┴─────── FAIL
│ │
ArtifactCommit ArtifactRollback
│ │
Idle Rebooting (old slot)
Bu bölümde
- Mender mimarisi: server + client + artifact repository
- /etc/mender/mender.conf: server URL, tenant token, partition config
- Device type: artifact uyumluluğu için cihaz kimliği
- State machine: Idle → Check → Download → Install → Reboot → Commit/Rollback
06 Mender artifact oluşturma
mender-artifact CLI aracı ile rootfs image'ını signed .mender dosyasına dönüştürebilir, artifact bağımlılıkları ve delta güncelleme tanımlayabilirsiniz.
Temel artifact oluşturma
# Basit rootfs artifact
mender-artifact write rootfs-image \
--file rootfs.ext4 \
--device-type raspberrypi4 \
--artifact-name firmware-v1.2.0 \
--output firmware-v1.2.0.mender
# Birden fazla device type
mender-artifact write rootfs-image \
--file rootfs.ext4 \
--device-type raspberrypi4 \
--device-type raspberrypi4-64 \
--artifact-name firmware-v1.2.0 \
--output firmware-v1.2.0.mender
İmzalı artifact
# RSA private key oluştur
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:3072 \
-out mender-private.pem
# Public key export
openssl rsa -in mender-private.pem -pubout -out mender-public.pem
# İmzalı artifact oluştur
mender-artifact write rootfs-image \
--file rootfs.ext4 \
--device-type raspberrypi4 \
--artifact-name firmware-v1.2.0 \
--signing-key mender-private.pem \
--output firmware-v1.2.0-signed.mender
# Artifact bilgilerini görüntüle
mender-artifact read firmware-v1.2.0-signed.mender
Update module ile özel artifact
# Tek dosya güncellemesi için file artifact
mender-artifact write module-image \
--type myapp-config \
--file /tmp/myapp.conf \
--device-type raspberrypi4 \
--artifact-name config-v2.0 \
--output config-update.mender
# Artifact bağımlılığı: bu artifact yüklenmeden önce rootfs v1.2.0 gerekli
mender-artifact write module-image \
--type myapp-config \
--file /tmp/myapp.conf \
--device-type raspberrypi4 \
--artifact-name config-v2.0 \
--depends rootfs-image.version:firmware-v1.2.0 \
--output config-update.mender
Delta güncelleme (mender-binary-delta)
# Delta artifact oluştur (v1.1 → v1.2)
mender-binary-delta \
--source rootfs-v1.1.ext4 \
--dest rootfs-v1.2.ext4 \
--output delta-v1.1-to-v1.2.mender \
--device-type raspberrypi4 \
--artifact-name delta-v1.1-to-v1.2
# Delta artifact genellikle %70-90 daha küçük olur
# 200 MB rootfs için 15-30 MB delta tipik
Bu bölümde
mender-artifact write rootfs-image: temel artifact oluşturma--signing-key mender-private.pem: RSA imzalama- module-image: özel artifact tipi ve bağımlılık tanımı
mender-binary-delta: delta artifact (yüzde 70-90 boyut azalması)
07 Mender server entegrasyonu
Mender hosted veya self-hosted sunucusu, artifact yönetimi, cihaz kimlik doğrulaması ve phased rollout için REST API ve web UI sunar.
Hosted Mender vs self-hosted
| Seçenek | Avantaj | Dezavantaj |
|---|---|---|
| hosted.mender.io | Hızlı başlangıç, yönetim yok | Ücretli (100+ cihaz), dışa bağımlılık |
| Self-hosted (Docker) | Tam kontrol, maliyet optimizasyonu | Altyapı yönetimi gerekli |
| Self-hosted (Kubernetes) | Yüksek ölçeklenebilirlik | DevOps uzmanlığı gerekli |
Self-hosted kurulumu
# Mender server (Docker Compose)
git clone https://github.com/mendersoftware/mender-server-enterprise.git
cd mender-server-enterprise
# Demo ortam için (production için özelleştir)
./run --demo
# mender-cli kurulumu (host makinede)
wget https://downloads.mender.io/mender-cli/latest/linux/mender-cli
chmod +x mender-cli
mv mender-cli /usr/local/bin/
# Server'a giriş
mender-cli login \
--server https://mender.mycompany.com \
--username admin@example.com
Artifact yükleme ve deployment
# Artifact yükle
mender-cli artifacts upload firmware-v1.2.0-signed.mender
# Yüklü artifact'ları listele
mender-cli artifacts list
# Belirli cihazlara deployment oluştur
mender-cli deployments create \
--name "Production v1.2.0" \
--artifact firmware-v1.2.0-signed \
--devices all-production
# Deployment durumu izle
mender-cli deployments list
REST API ile CI/CD entegrasyonu
#!/bin/bash
# CI pipeline: artifact yükle + deployment tetikle
MENDER_SERVER="${MENDER_SERVER_URL}"
MENDER_TOKEN="${MENDER_ACCESS_TOKEN}"
ARTIFACT_NAME="firmware-${CI_COMMIT_TAG}"
ARTIFACT_FILE="output/${ARTIFACT_NAME}.mender"
# 1. Artifact yükle
curl -s -X POST \
-H "Authorization: Bearer ${MENDER_TOKEN}" \
-F "artifact=@${ARTIFACT_FILE}" \
"${MENDER_SERVER}/api/management/v1/deployments/artifacts" || exit 1
# 2. Phased deployment oluştur (önce %10)
curl -s -X POST \
-H "Authorization: Bearer ${MENDER_TOKEN}" \
-H "Content-Type: application/json" \
-d "{
\"name\": \"${ARTIFACT_NAME}-phased\",
\"artifact_name\": \"${ARTIFACT_NAME}\",
\"filter\": {\"id\": \"all\"},
\"phases\": [
{\"batch_size\": 10, \"delay_seconds\": 7200},
{\"batch_size\": 50, \"delay_seconds\": 7200},
{\"batch_size\": 100}
]
}" \
"${MENDER_SERVER}/api/management/v2/deployments/deployments"
Bu bölümde
- hosted.mender.io vs self-hosted Docker karşılaştırması
mender-cli artifacts uploadve deployment oluşturma- REST API ile CI/CD entegrasyonu: artifact push + deployment tetikleme
- Phased rollout: yüzde 10 → 50 → 100 aşamalı deployment
08 RAUC (Robust Auto-Update Controller)
RAUC, systemd proje ailesinden gelen D-Bus arayüzlü güncelleme kontrolcüsüdür; GRUB, U-Boot ve Barebox bootloader'larıyla sorunsuz entegre olur.
RAUC mimarisi
.raucb bundle (SquashFS + manifest + imza)
│
▼
rauc install bundle.raucb
│
[verify bundle signature]
│
[parse manifest.raucm]
│
[write slots via handlers]
│
[update bootloader state via D-Bus]
│
[reboot → bootloader marks slot active]
Sistem konfigürasyonu
[system]
compatible=myboard-v1
bootloader=uboot
bundle-formats=verity
[keyring]
path=/etc/rauc/keyring.pem
[slot.rootfs.0]
device=/dev/mmcblk0p2
type=raw
bootname=A
[slot.rootfs.1]
device=/dev/mmcblk0p3
type=raw
bootname=B
[slot.boot.0]
device=/dev/mmcblk0p1
type=vfat
parent=rootfs.0
[slot.data.0]
device=/dev/mmcblk0p4
type=ext4
parent=rootfs.0
allow-mounted=true
RAUC bundle manifest
[update]
compatible=myboard-v1
version=2.0.0
description=MyBoard production firmware 2.0.0
[bundle]
format=verity
[image.rootfs]
filename=rootfs.squashfs
sha256=abcdef1234567890...
size=52428800
[image.boot]
filename=boot.vfat
sha256=0987654321fedcba...
size=67108864
Bundle oluşturma ve yükleme
# Bundle oluştur
rauc bundle \
--cert signing.pem \
--key signing.key \
./bundle-dir/ \
firmware-v2.0.raucb
# Bundle bilgisi
rauc info firmware-v2.0.raucb
# Yükle
rauc install firmware-v2.0.raucb
# Durum
rauc status
D-Bus API ile programatik güncelleme
# RAUC D-Bus interface
busctl introspect de.pengutronix.rauc / \
de.pengutronix.rauc.Installer
# Güncelleme başlat
busctl call de.pengutronix.rauc / \
de.pengutronix.rauc.Installer \
Install "s" /path/to/firmware-v2.0.raucb
# Durum sorgula
busctl call de.pengutronix.rauc / \
de.pengutronix.rauc.Installer \
GetSlotStatus
# Progress sinyalini dinle
busctl monitor de.pengutronix.rauc
RAUC, SWUpdate ve Mender karşılaştırması
| Özellik | SWUpdate | Mender | RAUC |
|---|---|---|---|
| D-Bus API | Hayır | Hayır | Evet |
| Bundle format | CPIO .swu | tar .mender | SquashFS .raucb |
| Server entegrasyonu | HawkBit | Mender server | HawkBit, özel |
| Yocto layer | meta-swupdate | meta-mender | meta-rauc |
| Delta güncelleme | zchunk (deneysel) | binary-delta | Yok (roadmap) |
Bu bölümde
- RAUC: systemd ailesi, D-Bus interface, SquashFS bundle
- /etc/rauc/system.conf: slot tanımları ve bootloader konfigürasyonu
- manifest.raucm: bundle içerik tarifi
busctl call de.pengutronix.rauc: D-Bus üzerinden programatik güncelleme- SWUpdate / Mender / RAUC karşılaştırma tablosu
09 Pratik: Raspberry Pi OTA pipeline
Raspberry Pi 4, Yocto, SWUpdate ve A/B eMMC ile gerçek bir OTA pipeline kuruyoruz; CI'dan cihaza tam otomasyonlu güncelleme akışı.
Hedef mimari
GitLab CI
├── Yocto build (core-image-minimal + meta-swupdate)
├── .swu artifact imzala
└── S3 bucket'a yükle
│ HTTPS
▼
Cihaz (Raspberry Pi 4)
├── swupdate-suricatta → S3 polling (her 6 saatte)
├── İndir + doğrula + yaz (Slot B)
├── fw_setenv boot_slot=B upgrade_available=1
└── reboot → health check → onayla
Yocto konfigürasyonu
MACHINE = "raspberrypi4-64"
DISTRO = "poky"
# SWUpdate katmanı
# meta-swupdate: https://github.com/sbabic/meta-swupdate
IMAGE_INSTALL:append = " \
swupdate \
swupdate-www \
u-boot-fw-utils \
health-check \
"
# SWUpdate suricatta (HawkBit veya özel) için
SWUPDATE_SURICATTA_CONFIG = "file:///etc/swupdate/suricatta.cfg"
# A/B için iki rootfs image
IMAGE_FSTYPES = "squashfs-xz ext4.gz"
GitLab CI pipeline
stages:
- build
- sign
- deploy
build-image:
stage: build
script:
- cd yocto
- source poky/oe-init-build-env build
- bitbake rpi4-ota-image
- cp tmp/deploy/images/raspberrypi4-64/*.squashfs-xz ../artifacts/
artifacts:
paths:
- artifacts/
sign-swu:
stage: sign
script:
- SHA=$(sha256sum artifacts/rootfs.squashfs-xz | cut -d' ' -f1)
- sed "s/ROOTFS_SHA256/$SHA/g" sw-description.tmpl > sw-description
- openssl cms -sign -in sw-description -out sw-description.sig
-signer $SIGNING_CERT -inkey $SIGNING_KEY
-outform DER -nosmimecap -binary
- echo "sw-description sw-description.sig artifacts/rootfs.squashfs-xz"
| tr ' ' '\n' | cpio -ovH newc > firmware-${CI_COMMIT_TAG}.swu
- aws s3 cp firmware-${CI_COMMIT_TAG}.swu
s3://my-ota-bucket/releases/
only:
- tags
Cihaz polling script
#!/bin/sh
# S3'ten güncel firmware kontrolü ve indirme
BUCKET_URL="https://my-ota-bucket.s3.amazonaws.com/releases"
DEVICE_TYPE="raspberrypi4"
CURRENT_VER=$(cat /etc/firmware-version 2>/dev/null || echo "0.0.0")
# Güncel versiyon dosyasını kontrol et
LATEST=$(curl -sf "${BUCKET_URL}/latest-${DEVICE_TYPE}.txt")
[ -z "$LATEST" ] && exit 0
[ "$LATEST" = "$CURRENT_VER" ] && exit 0
echo "Güncelleme mevcut: $CURRENT_VER → $LATEST"
# İndir
curl -f -o /data/tmp/firmware.swu \
"${BUCKET_URL}/firmware-${LATEST}.swu" || exit 1
# Uygula
swupdate -k /etc/swupdate/public.pem \
-i /data/tmp/firmware.swu
RC=$?
rm -f /data/tmp/firmware.swu
if [ $RC -eq 0 ]; then
echo "Güncelleme hazır, yeniden başlatılıyor..."
reboot
else
echo "Güncelleme başarısız: $RC"
fi
Health check servisi
#!/bin/sh
UPGRADE=$(fw_printenv upgrade_available 2>/dev/null | cut -d= -f2)
[ "$UPGRADE" != "1" ] && exit 0
echo "Post-update health check başlıyor..."
# 1. Kritik servisler
for svc in networking myapp ssh; do
if ! systemctl is-active --quiet "$svc"; then
echo "FAIL: $svc çalışmıyor"
fw_setenv upgrade_available 0
fw_setenv boot_slot A
reboot; exit 1
fi
done
# 2. Ağ bağlantısı
if ! ping -c 1 -W 5 8.8.8.8 >/dev/null 2>&1; then
echo "FAIL: Ağ yok"
fw_setenv upgrade_available 0
fw_setenv boot_slot A
reboot; exit 1
fi
# Tüm kontroller geçti
fw_setenv upgrade_available 0
fw_setenv bootcount 0
echo "Güncelleme onaylandı."
[Unit]
Description=OTA Health Check
After=network-online.target myapp.service
Wants=network-online.target
[Service]
Type=oneshot
ExecStartPre=/bin/sleep 30
ExecStart=/usr/bin/health-check.sh
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
Rollback senaryosunda bootcount limit aşıldığında U-Boot otomatik olarak eski slota döner. Ancak health-check.service'in başarısız olduğu durumları da izlemek gerekir. systemd'nin OnFailure= directive'i ile başarısızlık durumunda ek bir bildirim veya rollback tetikleyicisi kurulabilir.
Bu bölümde
- Hedef mimari: GitLab CI → Yocto build → S3 → SWUpdate polling
- GitLab CI: imzalı .swu oluşturma ve S3 upload
- Cihaz polling: S3'ten versiyon kontrolü ve indirme
- Health check: kritik servis + ağ kontrolü + otomatik rollback
- systemd service:
ExecStartPre=/bin/sleep 30ile geciktirilmiş kontrol