{"title":"Mart\u00edn's blog","subtitle":"Igalian blog.","link":[{"@attributes":{"href":"https:\/\/blogs.igalia.com\/mabente\/feed\/feed.xml","rel":"self"}},{"@attributes":{"href":"https:\/\/blogs.igalia.com\/mabente\/"}}],"updated":"2025-06-16T00:00:00Z","id":"https:\/\/blogs.igalia.com\/","author":{"name":"Mart\u00edn Abente Lahaye","email":"mabente@igalia.com"},"entry":{"title":"How to integrate systemd-sysupdate with your Yocto-based image","link":{"@attributes":{"href":"https:\/\/blogs.igalia.com\/mabente\/how-to-integrate-systemd-sysupdate-with-your-yocto-based-image\/"}},"updated":"2025-06-16T00:00:00Z","id":"https:\/\/blogs.igalia.com\/mabente\/how-to-integrate-systemd-sysupdate-with-your-yocto-based-image\/","content":"<p>The <a href=\"https:\/\/www.yoctoproject.org\/\">Yocto<\/a> project has well-established OS update mechanisms available via third-party <a href=\"https:\/\/wiki.yoctoproject.org\/wiki\/System_Update\">layers<\/a>. But, did you know that recent releases of Yocto already come with a simple update mechanism?<\/p>\n<p>The goal of this blog post is to present an alternative that doesn\u2019t require a third-party layer and explain how it can be integrated with your Yocto-based image.<\/p>\n<h2 id=\"systemd-sysupdate\" tabindex=\"-1\">systemd-sysupdate <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/mabente\/how-to-integrate-systemd-sysupdate-with-your-yocto-based-image\/\">#<\/a><\/h2>\n<p>Enter <a href=\"https:\/\/www.freedesktop.org\/software\/systemd\/man\/latest\/systemd-sysupdate.html\">systemd-sysupdate<\/a>: a mechanism capable of automatically discovering, downloading, and installing A\/B-style OS updates. In a nutshell, it provides:<\/p>\n<ul>\n<li>Atomic updates for a collection of different resources (files, directories or partitions).<\/li>\n<li>Updates from remote and local sources (HTTP\/HTTPS and directories).<\/li>\n<li>Parallel installed versions A\/B\/C\/\u2026 style.<\/li>\n<li>Relative small footprint (~10 MiB or roughly 5% increase in our demo image).<\/li>\n<li>Basic features are available since systemd 251 (released in May 2022).<\/li>\n<li>Optional built-in services for updating and rebooting.<\/li>\n<li>Optional DBus interface for applications integration.<\/li>\n<li>Optional grouping of resources to be enabled together as features.<\/li>\n<\/ul>\n<p>Together with automatic boot assessment, systemd-boot, and other tools, we can turn this OS update mechanism into a comprehensive alternative for common scenarios.<\/p>\n<h2 id=\"yocto-integration\" tabindex=\"-1\">Yocto integration <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/mabente\/how-to-integrate-systemd-sysupdate-with-your-yocto-based-image\/\">#<\/a><\/h2>\n<p>sysupdate has been available with Yocto releases for a few years now but, in order use it, it requires a few steps:<\/p>\n<ol>\n<li>Identifying the OS resources that need to be updated.<\/li>\n<li>Versioning these resources and the OS.<\/li>\n<li>Enabling sysupdate and providing transfer files for each resource.<\/li>\n<li>Serving updates via a web server.<\/li>\n<\/ol>\n<h3 id=\"os-resources-to-update\" tabindex=\"-1\">OS resources to update <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/mabente\/how-to-integrate-systemd-sysupdate-with-your-yocto-based-image\/\">#<\/a><\/h3>\n<p>The resources that need to be updated will depend on how the distribution is set up. For this post we\u2019re assuming the following:<\/p>\n<ul>\n<li>An image based on the latest Poky release, using systemd and systemd-boot.<\/li>\n<li>The kernel, commands, initramfs, and other boot-related files are provided via an <a href=\"https:\/\/github.com\/uapi-group\/specifications\/blob\/main\/specs\/unified_kernel_image.md\">Unified Kernel Image<\/a> (UKI).<\/li>\n<li>A single rootfs, using ext4.<\/li>\n<\/ul>\n<p>A Yocto-based image like this can be described as follows:<\/p>\n<p><strong>kas-poky-demo.yml<\/strong>:<\/p>\n<pre class=\"language-bash\" tabindex=\"0\"><code class=\"language-bash\">INIT_MANAGER <span class=\"token operator\">=<\/span> <span class=\"token string\">\"systemd\"<\/span><br>EFI_PROVIDER <span class=\"token operator\">=<\/span> <span class=\"token string\">\"systemd-boot\"<\/span><br>INITRAMFS_IMAGE <span class=\"token operator\">=<\/span> <span class=\"token string\">\"core-image-minimal-initramfs\"<\/span><br>QB_KERNEL_ROOT <span class=\"token operator\">=<\/span> <span class=\"token string\">\"\"<\/span><br>QB_DEFAULT_KERNEL <span class=\"token operator\">=<\/span> <span class=\"token string\">\"none\"<\/span><br>IMAGE_FSTYPES <span class=\"token operator\">=<\/span> <span class=\"token string\">\"wic\"<\/span><br>WKS_FILE <span class=\"token operator\">=<\/span> <span class=\"token string\">\"core-image-demo.wks.in\"<\/span><\/code><\/pre>\n<p><strong>recipes-core\/images\/core-image-demo.bb<\/strong>:<\/p>\n<pre class=\"language-bash\" tabindex=\"0\"><code class=\"language-bash\">SUMMARY <span class=\"token operator\">=<\/span> <span class=\"token string\">\"A demo image with UKI support enabled\"<\/span><br>LICENSE <span class=\"token operator\">=<\/span> <span class=\"token string\">\"MIT\"<\/span><br>UKI_CMDLINE <span class=\"token operator\">=<\/span> <span class=\"token string\">\"rootwait root=PARTLABEL=rootfs console=<span class=\"token variable\">${KERNEL_CONSOLE}<\/span>\"<\/span><br>inherit core-image uki<\/code><\/pre>\n<p><strong>wic\/core-image-demo.wks.in<\/strong>:<\/p>\n<pre class=\"language-bash\" tabindex=\"0\"><code class=\"language-bash\">part \/boot <span class=\"token parameter variable\">--ondisk<\/span> sda <span class=\"token parameter variable\">--fstype<\/span> vfat --part-name ESP --part-type c12a7328-f81f-11d2-ba4b-00a0c93ec93b <span class=\"token parameter variable\">--source<\/span> bootimg-efi <span class=\"token parameter variable\">--sourceparams<\/span><span class=\"token operator\">=<\/span><span class=\"token string\">\"loader=systemd-boot,install-kernel-into-boot-dir=false\"<\/span> <span class=\"token parameter variable\">--align<\/span> <span class=\"token number\">1024<\/span> <span class=\"token parameter variable\">--active<\/span> --fixed-size 100M<br>part \/ <span class=\"token parameter variable\">--ondisk<\/span> sda <span class=\"token parameter variable\">--fstype<\/span><span class=\"token operator\">=<\/span>ext4 <span class=\"token parameter variable\">--source<\/span> rootfs --part-name rootfs --part-type 4f68bce3-e8cd-4db1-96e7-fbcaf984b709 <span class=\"token parameter variable\">--align<\/span> <span class=\"token number\">1024<\/span> --use-uuid --fixed-size 300M<br>bootloader <span class=\"token parameter variable\">--ptable<\/span> gpt <span class=\"token parameter variable\">--timeout<\/span><span class=\"token operator\">=<\/span><span class=\"token number\">5<\/span><\/code><\/pre>\n<p>Under this specific setup, a full OS update would consist of the following resources:<\/p>\n<ul>\n<li>The UKI, a regular file to be updated under the <code>\/boot<\/code> partition.<\/li>\n<li>The rootfs, a partition that can be updated in its entirety.<\/li>\n<\/ul>\n<p>As mentioned before, updating files and partitions is supported by sysupdate. So, we\u2019re good.<\/p>\n<h3 id=\"versioning-resources-and-the-os\" tabindex=\"-1\">Versioning resources and the OS <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/mabente\/how-to-integrate-systemd-sysupdate-with-your-yocto-based-image\/\">#<\/a><\/h3>\n<p>In order for sysupdate to determine the current version of the OS, it looks for the os-release file and inspects it for an <a href=\"https:\/\/www.freedesktop.org\/software\/systemd\/man\/latest\/os-release.html#IMAGE_VERSION=\">IMAGE_VERSION<\/a> field. Therefore, the image version must be included.<\/p>\n<p>Resources that require updating must also be versioned with the image version. Following our previous assumptions:<\/p>\n<ul>\n<li>The UKI filename is suffixed with the image version (e.g., <code>uki_0.efi<\/code> where <code>0<\/code> is the image version).<\/li>\n<li>The rootfs partition is also versioned by suffixing the image version in its partition name (e.g., <code>rootfs_0<\/code> could be the initial name of the partition).<\/li>\n<\/ul>\n<p>To implement these changes in your Yocto-based image, the following recipes should be added or overridden:<\/p>\n<p><strong>recipes-core\/os-release\/os-release.bbappend:<\/strong><\/p>\n<pre class=\"language-bash\" tabindex=\"0\"><code class=\"language-bash\">OS_RELEASE_FIELDS <span class=\"token operator\">+=<\/span> <span class=\"token string\">\" \\<br>    IMAGE_VERSION \\<br>\"<\/span><br>OS_RELEASE_UNQUOTED_FIELDS <span class=\"token operator\">+=<\/span> <span class=\"token string\">\" \\<br>    IMAGE_VERSION \\<br>\"<\/span><\/code><\/pre>\n<p>Note that the value of <code>IMAGE_VERSION<\/code> can be hardcoded, provided by the continuous integration pipeline or determined at build-time (e.g., the current date and time).<\/p>\n<p><strong>recipes-core\/images\/core-image-demo.bb<\/strong>:<\/p>\n<pre class=\"language-diff\" tabindex=\"0\"><code class=\"language-diff\"><span class=\"token deleted-sign deleted\"><span class=\"token prefix deleted\">-<\/span><span class=\"token line\">UKI_CMDLINE = \"rootwait root=PARTLABEL=rootfs console=${KERNEL_CONSOLE}\"<br><\/span><\/span><span class=\"token inserted-sign inserted\"><span class=\"token prefix inserted\">+<\/span><span class=\"token line\">UKI_FILENAME = \"uki_${IMAGE_VERSION}.efi\"<br><\/span><span class=\"token prefix inserted\">+<\/span><span class=\"token line\">UKI_CMDLINE = \"rootwait root=PARTLABEL=rootfs_${IMAGE_VERSION} console=${KERNEL_CONSOLE}\"<\/span><\/span><\/code><\/pre>\n<p><strong>wic\/core-image-demo.wks.in<\/strong>:<\/p>\n<pre class=\"language-diff\" tabindex=\"0\"><code class=\"language-diff\"><span class=\"token deleted-sign deleted\"><span class=\"token prefix deleted\">-<\/span><span class=\"token line\">part \/ --ondisk sda --fstype=ext4 --source rootfs --part-name rootfs --part-type 4f68bce3-e8cd-4db1-96e7-fbcaf984b709 --align 1024 --use-uuid --fixed-size 300M<br><\/span><\/span><span class=\"token inserted-sign inserted\"><span class=\"token prefix inserted\">+<\/span><span class=\"token line\">part \/ --ondisk sda --fstype=ext4 --source rootfs --part-name \"rootfs_${IMAGE_VERSION}\" --part-type 4f68bce3-e8cd-4db1-96e7-fbcaf984b709 --align 1024 --use-uuid --fixed-size 300M<\/span><\/span><\/code><\/pre>\n<p>In the above recipes, we\u2019re adding the suffix to the UKI filename and partition name, and we\u2019re also coupling our UKI directly to its correspondent rootfs partition.<\/p>\n<h3 id=\"enabling-systemd-sysupdate\" tabindex=\"-1\">Enabling systemd-sysupdate <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/mabente\/how-to-integrate-systemd-sysupdate-with-your-yocto-based-image\/\">#<\/a><\/h3>\n<p>By default, sysupdate is disabled in Yocto\u2019s systemd <a href=\"https:\/\/git.yoctoproject.org\/poky\/plain\/meta\/recipes-core\/systemd\/\">recipe<\/a> and there are no \u201cdefault\u201d transfer files for sysupdate. Therefore you must:<\/p>\n<ol>\n<li>Override systemd build configuration options and dependencies.<\/li>\n<li>Write transfer files for each resource that needs to be updated.<\/li>\n<li>Extend the partitions kickstart file with an additional partition that must mirror the original rootfs partition. This is to support an A\/B OS update scheme.<\/li>\n<\/ol>\n<p>To implement these changes in your Yocto-based image, the following recipes should be added or modified:<\/p>\n<p><strong>recipes-core\/systemd\/systemd_%.bbappend<\/strong>:<\/p>\n<pre class=\"language-bash\" tabindex=\"0\"><code class=\"language-bash\">EXTRA_OEMESON:append <span class=\"token operator\">=<\/span> <span class=\"token string\">\" \\<br>    -Dfdisk=enabled \\<br>    -Dsysupdate=enabled \\<br>    -Dsysupdated=enabled \\<br>\"<\/span><br>SRC_URI <span class=\"token operator\">+=<\/span> <span class=\"token string\">\" \\<br>    file:\/\/60-rootfs.transfer \\<br>    file:\/\/70-kernel.transfer \\<br>\"<\/span><br>do_install:<span class=\"token function-name function\">append<\/span><span class=\"token punctuation\">(<\/span><span class=\"token punctuation\">)<\/span> <span class=\"token punctuation\">{<\/span><br>    <span class=\"token function\">install<\/span> <span class=\"token parameter variable\">-d<\/span> <span class=\"token variable\">${D}<\/span><span class=\"token variable\">${base_libdir}<\/span>\/sysupdate.d<br>    <span class=\"token function\">install<\/span> <span class=\"token parameter variable\">-m<\/span> 0644 <span class=\"token variable\">${UNPACKDIR}<\/span>\/60-rootfs.transfer <span class=\"token variable\">${D}<\/span><span class=\"token variable\">${base_libdir}<\/span>\/sysupdate.d\/<br>    <span class=\"token function\">install<\/span> <span class=\"token parameter variable\">-m<\/span> 0644 <span class=\"token variable\">${UNPACKDIR}<\/span>\/70-kernel.transfer <span class=\"token variable\">${D}<\/span><span class=\"token variable\">${base_libdir}<\/span>\/sysupdate.d\/<br><span class=\"token punctuation\">}<\/span><\/code><\/pre>\n<p>Note that some minor details are omitted from this snippet, but you can find the full source files down below.<\/p>\n<p><strong>recipes-core\/systemd\/systemd\/60-rootfs.transfer<\/strong>:<\/p>\n<pre class=\"language-bash\" tabindex=\"0\"><code class=\"language-bash\"><span class=\"token punctuation\">[<\/span>Transfer<span class=\"token punctuation\">]<\/span><br><span class=\"token assign-left variable\">ProtectVersion<\/span><span class=\"token operator\">=<\/span>%A<br><span class=\"token assign-left variable\">Verify<\/span><span class=\"token operator\">=<\/span>no<br><br><span class=\"token punctuation\">[<\/span>Source<span class=\"token punctuation\">]<\/span><br><span class=\"token assign-left variable\">Type<\/span><span class=\"token operator\">=<\/span>url-file<br><span class=\"token assign-left variable\">Path<\/span><span class=\"token operator\">=<\/span>http:\/\/10.0.2.2:3333\/<br><span class=\"token assign-left variable\">MatchPattern<\/span><span class=\"token operator\">=<\/span>rootfs_@v.ext4<br><br><span class=\"token punctuation\">[<\/span>Target<span class=\"token punctuation\">]<\/span><br><span class=\"token assign-left variable\">Type<\/span><span class=\"token operator\">=<\/span>partition<br><span class=\"token assign-left variable\">Path<\/span><span class=\"token operator\">=<\/span>auto<br><span class=\"token assign-left variable\">MatchPattern<\/span><span class=\"token operator\">=<\/span>rootfs_@v<br><span class=\"token assign-left variable\">MatchPartitionType<\/span><span class=\"token operator\">=<\/span>root<br><span class=\"token assign-left variable\">InstancesMax<\/span><span class=\"token operator\">=<\/span><span class=\"token number\">2<\/span><\/code><\/pre>\n<p><strong>recipes-core\/systemd\/systemd\/70-kernel.transfer<\/strong>:<\/p>\n<pre class=\"language-bash\" tabindex=\"0\"><code class=\"language-bash\"><span class=\"token punctuation\">[<\/span>Transfer<span class=\"token punctuation\">]<\/span><br><span class=\"token assign-left variable\">ProtectVersion<\/span><span class=\"token operator\">=<\/span>%A<br><span class=\"token assign-left variable\">Verify<\/span><span class=\"token operator\">=<\/span>no<br><br><span class=\"token punctuation\">[<\/span>Source<span class=\"token punctuation\">]<\/span><br><span class=\"token assign-left variable\">Type<\/span><span class=\"token operator\">=<\/span>url-file<br><span class=\"token assign-left variable\">Path<\/span><span class=\"token operator\">=<\/span>http:\/\/10.0.2.2:3333\/<br><span class=\"token assign-left variable\">MatchPattern<\/span><span class=\"token operator\">=<\/span>uki_@v.efi<br><br><span class=\"token punctuation\">[<\/span>Target<span class=\"token punctuation\">]<\/span><br><span class=\"token assign-left variable\">Type<\/span><span class=\"token operator\">=<\/span>regular-file<br><span class=\"token assign-left variable\">Path<\/span><span class=\"token operator\">=<\/span>\/EFI\/Linux<br><span class=\"token assign-left variable\">PathRelativeTo<\/span><span class=\"token operator\">=<\/span>boot<br><span class=\"token assign-left variable\">MatchPattern<\/span><span class=\"token operator\">=<\/span>uki_@v+@l-@d.efi uki_@v+@l.efi uki_@v.efi<br><span class=\"token assign-left variable\">Mode<\/span><span class=\"token operator\">=<\/span>0444<br><span class=\"token assign-left variable\">TriesLeft<\/span><span class=\"token operator\">=<\/span><span class=\"token number\">3<\/span><br><span class=\"token assign-left variable\">TriesDone<\/span><span class=\"token operator\">=<\/span><span class=\"token number\">0<\/span><br><span class=\"token assign-left variable\">InstancesMax<\/span><span class=\"token operator\">=<\/span><span class=\"token number\">2<\/span><\/code><\/pre>\n<p>These transfer files define what exactly constitutes a full OS update. Each file contains the following sections:<\/p>\n<ul>\n<li>The transfer section, which defines general properties of the transfer (e.g., the fact the <code>A<\/code> version can\u2019t be overridden).<\/li>\n<li>The source section, which defines where to look for updates for these resources (e.g., a specific URL with matching pattern).<\/li>\n<li>The target section, which defines where these updated resources must go to (e.g., a partition that matches the naming pattern).<\/li>\n<\/ul>\n<p>For more information about these section properties check the <a href=\"https:\/\/www.freedesktop.org\/software\/systemd\/man\/latest\/sysupdate.d.html\">sysupdate.d<\/a> documentation.<\/p>\n<p><strong>wic\/core-image-demo.wks.in:<\/strong>:<\/p>\n<pre class=\"language-diff\" tabindex=\"0\"><code class=\"language-diff\"><span class=\"token unchanged\"><span class=\"token prefix unchanged\"> <\/span><span class=\"token line\">part \/ --ondisk sda --fstype=ext4 --source rootfs --part-name \"rootfs_${IMAGE_VERSION}\" --part-type 4f68bce3-e8cd-4db1-96e7-fbcaf984b709 --align 1024 --use-uuid --fixed-size 300M<br><\/span><\/span><span class=\"token inserted-sign inserted\"><span class=\"token prefix inserted\">+<\/span><span class=\"token line\">part --ondisk sda --source empty --part-name \"_empty\" --part-type 4f68bce3-e8cd-4db1-96e7-fbcaf984b709 --align 1024 --use-uuid --fixed-size 300M<\/span><\/span><\/code><\/pre>\n<p>Note that the <code>_empty<\/code> partition name is sysupdate\u2019s naming convention for the partition <a href=\"https:\/\/www.freedesktop.org\/software\/systemd\/man\/latest\/sysupdate.d.html#Resource%20Types\">resource type<\/a>.<\/p>\n<h3 id=\"serving-the-updates\" tabindex=\"-1\">Serving the updates <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/mabente\/how-to-integrate-systemd-sysupdate-with-your-yocto-based-image\/\">#<\/a><\/h3>\n<p>Updates can be served locally via regular directories or remotely via a regular HTTP\/HTTPS web server. For Over-the-air (OTA) updates, HTTP\/HTTPS is the correct option. Any web server can be used.<\/p>\n<pre class=\"language-bash\" tabindex=\"0\"><code class=\"language-bash\"><span class=\"token function\">ls<\/span> <span class=\"token parameter variable\">-1<\/span> .\/server\/<br>rootfs_0.ext4<br>rootfs_1.ext4<br>SHA256SUMS<br>uki_0.efi<br>uki_1.efi<\/code><\/pre>\n<p>When using HTTP\/HTTPS, sysupdate will request a <code>SHA256SUMS<\/code> checksum file. This file acts as the update server\u2019s \u201cmanifest\u201d, describing what updated resources are available.<\/p>\n<pre class=\"language-bash\" tabindex=\"0\"><code class=\"language-bash\">sha256sum * <span class=\"token operator\">><\/span> SHA256SUMS<br>python3 <span class=\"token parameter variable\">-m<\/span> http.server <span class=\"token number\">3333<\/span><\/code><\/pre>\n<h2 id=\"demo\" tabindex=\"-1\">Demo <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/mabente\/how-to-integrate-systemd-sysupdate-with-your-yocto-based-image\/\">#<\/a><\/h2>\n<p>If you\u2019re interested in seeing these steps in action, watch our presentation at Embedded Recipes 2025 from last May.<\/p>\n<p><a href=\"https:\/\/www.youtube.com\/watch?v=gx95md9TEWE\"><img src=\"https:\/\/img.youtube.com\/vi\/gx95md9TEWE\/0.jpg\" alt=\"Recording from Embedded Recipes 2025\"><\/a><\/p>\n<h2 id=\"demo-source-files\" tabindex=\"-1\">Demo source files <a class=\"header-anchor\" href=\"https:\/\/blogs.igalia.com\/mabente\/how-to-integrate-systemd-sysupdate-with-your-yocto-based-image\/\">#<\/a><\/h2>\n<p>The source files of the demo shown here and in the presentation are available on <a href=\"https:\/\/github.com\/Igalia\/yocto-sysupdate-demo\">GitHub<\/a>. Give it a try!<\/p>\n"}}