00 Üretim OTA gereksinimleri
Sahadaki gömülü cihazlara güvenilir yazılım güncellemesi göndermek, geliştirme ortamında test etmekten çok daha zorlu bir mühendislik problemidir. Bir güncelleme sistemi üretim ortamında arızalanmamalı, arızalanırsa kurtarabilmelidir.
Temel gereksinimler
Mevcut OTA yöntemleri karşılaştırması
| Yöntem | Atomik | Rollback | İmzalama | Üretim uygunluğu |
|---|---|---|---|---|
| Manuel FTP/SCP kopyalama | Hayır | Hayır | Hayır | Uygun değil |
| apt/opkg (paket yöneticisi) | Kısmi | Kısmi | Paket imzası | Sınırlı |
| SWUpdate | Evet | Evet | RSA/ECDSA | Tam uygun |
| RAUC | Evet | Evet | X.509 | Tam uygun |
| OSTree | Evet | Evet | GPG | Tam uygun |
SWUpdate, esnek handler sistemi ile karmaşık özel güncelleme iş akışlarına uygundur. RAUC, daha katı bundle formatı ile sadelik ve güvenlik odaklıdır. Yocto ekosistemiyle her ikisi de iyi entegre olur; meta-swupdate ve meta-rauc katmanları mevcuttur.
01 SWUpdate mimarisi
SWUpdate, C ile yazılmış bir daemon'dır. Güncelleme paketi (CPIO arşivi) işler, doğrular ve handler sistemi aracılığıyla cihaza uygular.
┌─────────────────────────────────────────────────────────────────┐
│ swupdate daemon │
│ │
│ ┌─────────┐ ┌──────────┐ ┌────────────┐ ┌──────────────┐ │
│ │Mongoose │ │ SURICATTA│ │Lua/Python │ │ DBus/Notif. │ │
│ │ Web UI │ │ hawkBit │ │ Scripting │ │ Interface │ │
│ └────┬────┘ └────┬─────┘ └─────┬──────┘ └──────────────┘ │
│ └────────────┴──────────────┘ │
│ │ │
│ ┌──────────▼──────────┐ │
│ │ swupdate core │ │
│ │ - CPIO parse │ │
│ │ - sw-description │ │
│ │ - signature verify │ │
│ └──────────┬──────────┘ │
│ │ │
│ ┌─────────────────┼──────────────────┐ │
│ │ │ │ │
│ ┌──▼──────┐ ┌───────▼────────┐ ┌──────▼─────────┐ │
│ │ ubivol │ │ raw / rawfile │ │ shellscript │ │
│ │ handler │ │ handler │ │ handler │ │
│ └─────────┘ └────────────────┘ └────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
Yerleşik handler'lar
| Handler | Kullanım | Notlar |
|---|---|---|
| ubivol | UBI volume güncelleme | NAND flash için ideal |
| raw | Ham blok yazma (/dev/mmcblk0p2) | eMMC, SD kart için |
| rawfile | Dosya sistemi üzerine dosya kopyalama | Tek dosya güncelleme |
| shellscript | Özel shell scripti çalıştırma | Esneklik sağlar |
| lua | Lua scripti ile özel mantık | Güçlü ve güvenli |
| bootloader | U-Boot ortam değişkeni yazma | Rollback için kritik |
| copymounted | Mount edilen fs üzerine tar | Overlay/incremental güncelleme |
| delta | libzsync delta güncelleme | Bant genişliği tasarrufu |
swupdate paket formatı
update.swu (CPIO arşivi)
├── sw-description # libconfig sözdizimi, güncelleme tarifi
├── sw-description.sig # RSA/ECDSA imzası
├── rootfs.ext4.gz # rootfs image
├── kernel.itb # FIT image (kernel + dtb)
├── bootloader.bin # U-Boot binary (opsiyonel)
└── post_install.lua # Özel Lua scripti (opsiyonel)
## Paket oluşturma
for f in sw-description sw-description.sig rootfs.ext4.gz kernel.itb; do
echo "$f"
done | cpio -ovH newc > update.swu
02 sw-description dosyası
sw-description, SWUpdate paketinin güncelleme tarifini içerir. libconfig sözdiziminde yazılır ve hangi image'ın nereye yazılacağını, doğrulama kontrollerini ve özel scriptleri tanımlar.
software =
{
version = "2.1.0";
description = "Üretim firmware v2.1.0";
/* Donanım revizyonu kontrolü */
hardware-compatibility = [ "1.0", "1.1", "1.2" ];
stable: {
images: (
{
/* Kernel FIT image */
filename = "kernel.itb";
type = "raw";
device = "/dev/mmcblk0p3"; /* kernel_b */
sha256 = "abc123def456...";
installed-directly = false;
compressed = false;
},
{
/* Root filesystem */
filename = "rootfs.ext4.gz";
type = "raw";
device = "/dev/mmcblk0p4"; /* rootfs_b */
sha256 = "def456abc789...";
compressed = true;
}
);
scripts: (
{
filename = "post_install.lua";
type = "lua";
}
);
/* Bootloader değişkeni — A/B geçişi */
bootenv: (
{
name = "boot_slot";
value = "b";
},
{
name = "bootcount";
value = "0";
}
);
};
}
Çoklu donanım profili
software =
{
version = "2.1.0";
/* Her donanım revizyonu için farklı image */
hardware: "rev-1.0" {
images: (
{
filename = "rootfs-rev1.ext4.gz";
type = "raw";
device = "/dev/mmcblk0p2";
sha256 = "aaa111bbb222...";
compressed = true;
}
);
};
hardware: "rev-2.0" {
images: (
{
filename = "rootfs-rev2.ext4.gz";
type = "raw";
device = "/dev/mmcblk0p2";
sha256 = "ccc333ddd444...";
compressed = true;
}
);
};
}
swupdate çalıştırma
## Yerel dosyadan güncelleme
swupdate -i update.swu -v
## Daemon modunda çalıştır (hawkBit için)
swupdate -f /etc/swupdate.cfg &
## İmza doğrulama ile
swupdate -i update.swu -k /etc/swupdate.pem -v
## Web sunucusunu etkinleştir (port 8080)
swupdate -w "--port 8080" &
## HTTP URL'den doğrudan indir ve uygula
swupdate -d "-u http://update-server/v2.1.0.swu"
03 SWUpdate Lua handler
Lua handler, sw-description'da script type ile tanımlanan özel güncelleme mantığını uygulamak için kullanılır. SWUpdate API'sine tam erişim sağlar.
-- post_install.lua
local swupdate = require("swupdate")
local posix = require("posix")
local INFO = swupdate.INFO
local WARNING = swupdate.WARNING
local ERROR = swupdate.ERROR
-- Güncelleme öncesi doğrulama
function preinst(image)
swupdate.notify(INFO, "Pre-install: " .. image.filename)
-- Disk alanı kontrolü (min 100 MB)
local stat = posix.statvfs("/")
local free_mb = (stat.f_bavail * stat.f_bsize) / (1024 * 1024)
if free_mb < 100 then
swupdate.notify(ERROR, "Yetersiz disk: " .. free_mb .. " MB")
return false
end
swupdate.notify(INFO, "Disk alanı yeterli: " .. free_mb .. " MB")
return true
end
-- Güncelleme sonrası adımlar
function postinst(image)
swupdate.notify(INFO, "Post-install başlıyor")
-- Bootloader ortam değişkenini güncelle
local rc = os.execute("fw_setenv boot_slot b")
if rc ~= 0 then
swupdate.notify(ERROR, "fw_setenv başarısız!")
return false
end
os.execute("fw_setenv bootcount 0")
-- Güncelleme logunu kaydet
local log = io.open("/data/update_log.txt", "a")
if log then
log:write(os.date() .. " — v2.1.0 kuruldu\n")
log:close()
end
swupdate.notify(INFO, "Post-install tamamlandı")
return true
end
return {
pre = preinst,
post = postinst,
}
Özel C handler — FPGA bitstream yazma
#include "swupdate.h"
#include "handler.h"
#include "util.h"
/* FPGA bitstream yazma handler'ı */
static int fpga_handler(struct img_type *img,
void __attribute__((__unused__)) *data)
{
int fd;
TRACE("FPGA bitstream yazma: %s", img->fname);
/* FPGA manager sysfs üzerinden yaz */
fd = open("/sys/class/fpga_manager/fpga0/firmware", O_WRONLY);
if (fd < 0) {
ERROR("FPGA manager açılamadı: %s", strerror(errno));
return -EFAULT;
}
long long written = copyfile(img, &fd, img->size, &img->offset,
0, 0, NULL);
close(fd);
if (written < 0) {
ERROR("FPGA yazma hatası");
return written;
}
INFO("FPGA bitstream başarıyla yazıldı: %lld byte", written);
return 0;
}
/* Başlangıçta handler'ı kaydet */
__attribute__((constructor))
static void fpga_handler_init(void)
{
register_handler("fpga", fpga_handler, IMAGE_HANDLER, NULL);
}
04 hawkBit entegrasyonu
hawkBit, Bosch tarafından geliştirilen açık kaynak bir IoT yazılım güncelleme sunucusudur. SWUpdate'in suricatta modülü ile entegre çalışır ve binlerce cihaza rollout yönetimi sağlar.
hawkBit Docker kurulumu
version: "3"
services:
hawkbit:
image: hawkbit/hawkbit-update-server:latest
ports:
- "8080:8080"
environment:
- SPRING_DATASOURCE_URL=jdbc:mariadb://db:3306/hawkbit
- SPRING_DATASOURCE_USERNAME=hawkbit
- SPRING_DATASOURCE_PASSWORD=hawkbit
- HAWKBIT_SERVER_SECURITY_DOS_FILTER_ENABLED=false
depends_on:
- db
- rabbitmq
db:
image: mariadb:10.11
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: hawkbit
MYSQL_USER: hawkbit
MYSQL_PASSWORD: hawkbit
volumes:
- dbdata:/var/lib/mysql
rabbitmq:
image: rabbitmq:3-management
ports:
- "15672:15672"
volumes:
dbdata:
SWUpdate suricatta konfigürasyonu
globals:
{
verbose = true;
loglevel = 3;
syslog = true;
};
suricatta:
{
url = "http://hawkbit-server:8080";
id = "device-001";
tenant = "DEFAULT";
polldelay = 45; /* saniye */
/* Kimlik doğrulama token'ı */
/* targettoken = "abc123..."; */
/* TLS için */
/* sslkey = "/etc/swupdate/device.key"; */
/* sslcert = "/etc/swupdate/device.crt"; */
/* cafile = "/etc/swupdate/ca.crt"; */
retry = 3;
retry_sleep = 1000;
};
identify:
{
name = "firmware-version";
value = "2.0.0";
};
Rollout oluşturma — DDI API
#!/usr/bin/env python3
import requests
BASE = "http://hawkbit-server:8080"
AUTH = ("admin", "admin")
# Distribution set oluştur
ds = requests.post(f"{BASE}/rest/v1/distributionsets",
json=[{"name": "firmware-v2.1.0", "version": "2.1.0",
"type": "os"}], auth=AUTH).json()[0]
ds_id = ds["id"]
# Rollout — %10 pilot grubu
rollout = requests.post(f"{BASE}/rest/v1/rollouts",
json={
"name": "rollout-v2.1.0",
"distributionSetId": ds_id,
"targetFilterQuery": "tag==production",
"deploymentGroups": [
{
"name": "pilot-10pct",
"targetPercentage": 10,
"successCondition": {"condition": "THRESHOLD",
"expression": "80"},
"errorCondition": {"condition": "THRESHOLD",
"expression": "5"},
"successAction": {"expression": "NEXTGROUP"},
"errorAction": {"expression": "PAUSE"}
},
{
"name": "full-rollout",
"targetPercentage": 100,
"successCondition": {"condition": "THRESHOLD",
"expression": "90"},
"errorCondition": {"condition": "THRESHOLD",
"expression": "5"},
"successAction": {"expression": "NEXTGROUP"},
"errorAction": {"expression": "PAUSE"}
}
],
"type": "forced"
}, auth=AUTH).json()
rollout_id = rollout["id"]
# Rollout başlat
requests.post(f"{BASE}/rest/v1/rollouts/{rollout_id}/start", auth=AUTH)
print(f"Rollout başlatıldı: ID={rollout_id}")
05 RAUC mimarisi
RAUC (Robust Auto-Update Controller), atomik A/B partition güncellemesi için tasarlanmış bir framework'tür. Güvenlik odaklı tasarımı ve net slot kavramıyla öne çıkar.
┌───────────────────────────────────────────────────────────┐
│ RAUC Bundle (.raucb) │
│ ┌──────────────────────────────────────────────────┐ │
│ │ manifest.raucm (X.509 imzalanmış) │ │
│ │ rootfs.img.verity │ kernel.img │ boot.img │ │
│ └──────────────────────────────────────────────────┘ │
└──────────────────────────┬────────────────────────────────┘
│ rauc install bundle.raucb
┌─────────▼──────────┐
│ RAUC Service │
│ (DBus daemon) │
└─────────┬──────────┘
│
┌───────────────┼──────────────────┐
│ │ │
┌──────────▼──────────┐ │ ┌─────────────▼────────────┐
│ Slot A (aktif) │ │ │ Slot B (pasif — hedef) │
│ /dev/mmcblk0p2 │ │ │ /dev/mmcblk0p3 │
│ rootfs_a, kernel_a │ │ │ rootfs_b, kernel_b │
└──────────────────────┘ │ └──────────────────────────┘
│
┌──────────▼──────────┐
│ Bootloader (U-Boot) │
│ boot_slot=b │
│ BOOT_ORDER=B A │
└─────────────────────┘
RAUC temel kavramlar
RAUC sistem konfigürasyonu
[system]
compatible=My-Board-v1
bootloader=uboot
[keyring]
path=/etc/rauc/ca.cert.pem
[handlers]
post-install=/usr/lib/rauc/post-install.sh
[slot.rootfs.0]
device=/dev/mmcblk0p2
type=ext4
bootname=A
parent=kernel.0
[slot.rootfs.1]
device=/dev/mmcblk0p3
type=ext4
bootname=B
parent=kernel.1
[slot.kernel.0]
device=/dev/mmcblk0p5
type=raw
bootname=A
[slot.kernel.1]
device=/dev/mmcblk0p6
type=raw
bootname=B
06 RAUC bundle oluşturma
RAUC bundle, imzalanmış bir manifest ve payload image'larından oluşur. X.509 sertifikaları ile imzalanır; doğrulama cihazda yerleşik CA sertifikasıyla yapılır.
X.509 sertifika altyapısı
## CA oluştur (üretimde HSM kullanın!)
openssl req -x509 -newkey rsa:4096 -keyout ca.key.pem \
-out ca.cert.pem -days 3650 -nodes \
-subj "/CN=RAUC CA/O=My Company/C=TR"
## İmzalama sertifikası oluştur
openssl genrsa -out developer.key.pem 2048
openssl req -new -key developer.key.pem \
-out developer.csr.pem \
-subj "/CN=RAUC Developer/O=My Company/C=TR"
## CA ile imzala
openssl x509 -req -in developer.csr.pem \
-CA ca.cert.pem -CAkey ca.key.pem \
-CAcreateserial -out developer.cert.pem -days 365
## CA sertifikası cihaza gömülür:
## /etc/rauc/ca.cert.pem
manifest.raucm
[update]
compatible=My-Board-v1
version=2.1.0
description=Üretim firmware v2.1.0
build=20260412T123456
[bundle]
format=verity
[image.rootfs]
filename=rootfs.ext4
digest=sha256:abc123def456789...
size=67108864
[image.kernel]
filename=kernel.img
digest=sha256:789abc012def345...
size=8388608
Bundle oluşturma scripti
#!/bin/bash
set -euo pipefail
VERSION="2.1.0"
BUNDLE_DIR="bundle_tmp"
BUNDLE_OUT="firmware-${VERSION}.raucb"
mkdir -p "${BUNDLE_DIR}"
cp rootfs.ext4 "${BUNDLE_DIR}/"
cp kernel.img "${BUNDLE_DIR}/"
cp manifest.raucm "${BUNDLE_DIR}/"
## Bundle oluştur ve imzala
rauc bundle \
--cert=developer.cert.pem \
--key=developer.key.pem \
"${BUNDLE_DIR}" \
"${BUNDLE_OUT}"
rauc info "${BUNDLE_OUT}"
echo "Oluşturuldu: ${BUNDLE_OUT} ($(du -sh ${BUNDLE_OUT} | cut -f1))"
rm -rf "${BUNDLE_DIR}"
Bundle kurma
## Bundle bilgisini görüntüle
rauc info firmware-2.1.0.raucb
## Slot durumunu görüntüle
rauc status
## Bundle'ı kur (pasif slot'a)
rauc install firmware-2.1.0.raucb
## Kurulum sonrası slot durumu
rauc status
## [slot.rootfs.1] device=/dev/mmcblk0p3 state=inactive boot_good=0
## Reboot → yeni slot denenir
reboot
07 A/B partition yönetimi
A/B partition şeması, üretim sistemlerinde kesintisiz güncellemeyi mümkün kılar. RAUC, bootloader ile iş birliği yaparak hangi slot'un aktif olduğunu ve rollback durumunu yönetir.
U-Boot bootcount mekanizması
## U-Boot ortam değişkenleri
boot_slot=b # Sonraki boot için hedef slot
bootlimit=3 # Max deneme sayısı
bootcount=1 # Mevcut deneme sayısı
boot_a_ok=1 # A slot'u iyi mi?
boot_b_ok=0 # B slot'u iyi mi? (yeni kuruldu)
## U-Boot boot logic (include/configs/my_board.h içinde):
##
## if (bootcount >= bootlimit) {
## /* Tüm denemeler tükendi, güvenli slot'a dön */
## boot_slot = boot_a_ok ? "a" : "b";
## saveenv(); reset();
## }
## bootcount++;
## saveenv();
## /* Hedef slot'u yükle */
## if (boot_slot == "b") {
## load_kernel_b(); load_dtb_b();
## bootargs += " root=/dev/mmcblk0p3";
## } else {
## load_kernel_a(); load_dtb_a();
## bootargs += " root=/dev/mmcblk0p2";
## }
RAUC post-install hook
#!/bin/sh
# RAUC çevre değişkenleri otomatik olarak ayarlanır:
# RAUC_SLOT_NAME, RAUC_SLOT_BOOTNAME, RAUC_BUNDLE_VERSION
set -e
BOOT_NAME="${RAUC_SLOT_BOOTNAME}" # A veya B
BOOT_LC=$(echo "${BOOT_NAME}" | tr '[:upper:]' '[:lower:]')
echo "RAUC post-install: slot=${RAUC_SLOT_NAME} boot=${BOOT_NAME}"
## Bootloader ortam değişkenlerini ayarla
fw_setenv boot_slot "${BOOT_LC}"
fw_setenv bootcount 0
fw_setenv "boot_${BOOT_LC}_ok" 0
echo "Bootloader güncellendi. Sonraki boot: slot ${BOOT_NAME}"
mark-good — başarılı boot onayı
## Başarılı boot onayı (systemd servisi çağırır)
rauc mark good booted
## Manuel: belirli slot'u kötü işaretle → rollback tetikle
rauc mark bad rootfs.1
## Slot durumu özeti
rauc status --output-format=json | python3 -m json.tool
## Aktif slot
rauc status | grep "booted slot"
Otomatik mark-good — systemd
[Unit]
Description=RAUC Mark Booted Slot as Good
After=network-online.target app-healthcheck.service
Wants=network-online.target
[Service]
Type=oneshot
# Uygulama sağlık kontrolü geçtikten sonra çalışır
ExecStart=/usr/bin/rauc mark good booted
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
rauc mark good booted komutu, sistemin başarıyla başladığını ve uygulamanın çalıştığını doğruladıktan sonra çağrılmalıdır. Erken çağrılırsa hatalı bir güncelleme kalıcı hale gelir. Uygulama sağlık kontrollerini geçtikten sonra çağırın.
08 Pratik: Yocto + RAUC + hawkBit CI/CD
Tam bir üretim OTA pipeline'ı: Yocto meta-rauc katmanı ile RAUC entegrasyonu, GitLab CI ile otomatik bundle üretimi ve hawkBit üzerinden rollout.
Yocto meta-rauc entegrasyonu
BBLAYERS ?= " \
${BSPDIR}/sources/poky/meta \
${BSPDIR}/sources/poky/meta-poky \
${BSPDIR}/sources/meta-openembedded/meta-oe \
${BSPDIR}/sources/meta-openembedded/meta-python \
${BSPDIR}/sources/meta-rauc \
${BSPDIR}/sources/meta-my-machine \
"
## RAUC ve hawkBit agent ekle
IMAGE_INSTALL:append = " rauc rauc-hawkbit-updater"
## A/B wic imajı için
IMAGE_FSTYPES:append = " wic"
WKS_FILE = "my-board-ab.wks"
## RAUC sertifikası (keyring)
RAUC_KEYRING_FILE = "${LAYERDIR}/files/ca.cert.pem"
## my-board-ab.wks — wic partition tanımı
# Boot partition (ortak, U-Boot + env)
part /boot --source bootimg-partition --ondisk mmcblk0 \
--fstype=vfat --label boot --active --align 4096 --size 64M
# Slot A — rootfs (aktif)
part / --source rootfs --ondisk mmcblk0 \
--fstype=ext4 --label rootfs_a --align 4096 --size 512M
# Slot B — rootfs (güncelleme hedefi)
part --ondisk mmcblk0 \
--fstype=ext4 --label rootfs_b --align 4096 --size 512M
# Kernel A
part --ondisk mmcblk0 \
--fstype=ext4 --label kernel_a --align 4096 --size 16M
# Kernel B
part --ondisk mmcblk0 \
--fstype=ext4 --label kernel_b --align 4096 --size 16M
# Kalıcı veri (güncellemeden etkilenmez)
part /data --ondisk mmcblk0 \
--fstype=ext4 --label data --align 4096
GitLab CI/CD pipeline
stages:
- build
- bundle
- sign
- deploy
variables:
YOCTO_MACHINE: "my-board"
BUNDLE_VERSION: "${CI_COMMIT_SHORT_SHA}"
HAWKBIT_URL: "http://hawkbit-server:8080"
## Yocto imajı ve RAUC bundle derle
build-image:
stage: build
image: crops/poky:debian-11
script:
- source oe-init-build-env
- bitbake core-image-minimal
- bitbake rauc-bundle
artifacts:
paths:
- build/tmp/deploy/images/${YOCTO_MACHINE}/*.raucb
- build/tmp/deploy/images/${YOCTO_MACHINE}/*.wic.gz
expire_in: 7 days
only:
- main
- tags
## Bundle'ı CI imzalama anahtarıyla imzala
sign-bundle:
stage: sign
image: debian:12-slim
before_script:
- apt-get install -y rauc openssl
script:
- UNSIGNED="build/tmp/deploy/images/${YOCTO_MACHINE}/rauc-bundle.raucb"
- SIGNED="firmware-${BUNDLE_VERSION}.raucb"
## Gizli anahtarlar CI/CD secret değişkenlerinden gelir
- echo "${RAUC_SIGNING_KEY}" | base64 -d > /tmp/signing.key.pem
- echo "${RAUC_SIGNING_CERT}" | base64 -d > /tmp/signing.cert.pem
- rauc resign
--key=/tmp/signing.key.pem
--cert=/tmp/signing.cert.pem
"${UNSIGNED}" "${SIGNED}"
- rauc info "${SIGNED}"
artifacts:
paths:
- firmware-*.raucb
expire_in: 30 days
only:
- tags
## hawkBit'e yükle ve pilot rollout başlat
deploy-hawkbit:
stage: deploy
image: python:3.11-slim
before_script:
- pip install requests
script:
- python3 ci/hawkbit_upload.py
--bundle "firmware-${BUNDLE_VERSION}.raucb"
--server "${HAWKBIT_URL}"
--version "${BUNDLE_VERSION}"
environment:
name: production
url: ${HAWKBIT_URL}
only:
- tags
CI bundle yükleme scripti
#!/usr/bin/env python3
import requests, argparse
def upload(server, bundle_path, version):
auth = ("admin", "admin")
base = f"{server}/rest/v1"
## 1. Software module oluştur
sm = requests.post(f"{base}/softwaremodules",
json=[{"name": f"firmware-{version}",
"version": version, "type": "os"}],
auth=auth).json()[0]
sm_id = sm["id"]
## 2. Bundle dosyasını yükle
with open(bundle_path, "rb") as f:
requests.post(f"{base}/softwaremodules/{sm_id}/artifacts",
files={"file": (bundle_path, f, "application/octet-stream")},
auth=auth)
## 3. Distribution set oluştur
ds = requests.post(f"{base}/distributionsets",
json=[{"name": f"firmware-{version}", "version": version,
"type": "os", "modules": [{"id": sm_id}]}],
auth=auth).json()[0]
print(f"Bundle hazır — DS ID={ds['id']} version={version}")
return ds["id"]
if __name__ == "__main__":
p = argparse.ArgumentParser()
p.add_argument("--bundle")
p.add_argument("--server")
p.add_argument("--version")
args = p.parse_args()
upload(args.server, args.bundle, args.version)
SWUpdate ve RAUC, üretim kalitesinde OTA güncelleme için tüm gereksinimleri karşılar: atomik güncelleme, kriptografik imzalama, otomatik rollback ve güç kesintisi güvenliği. RAUC'un verity bundle formatı ve dm-verity entegrasyonu özellikle güvenlik odaklı projelerde öne çıkar. Yocto + meta-rauc + GitLab CI + hawkBit kombinasyonu, endüstriyel IoT projeleri için olgun bir CI/CD pipeline'ı sunar.