abupdate_web_logo

A/B Linux OS update using RAUC and GRUB

v1.0.0

Table of contents

Overview

This repository is a guide on how to build an A/B update system with RAUC and GRUB2 on x86_64 UEFI platforms. An A/B update mechanism makes it possible to update the OS without booting into a separate minimal OS (e.g. a recovery system), and keeps the device bootable if an update turns out to be broken. It has been tested on an ASUS AAEON board with an Intel x86_64 CPU.

Versions

Table 1 - Document versions.

Version Release date What’s new
1.0.0 14.08.2026 - Initial v1.0.0 release.

Initial setup on the device

Set up the bootloader

There are two ways to set up the bootloader. The first one is to install it on a live device:

mkdir /tmp/grub
sudo mount /dev/sda1 /tmp/grub
sudo grub-install --target=x86_64-efi \
  --efi-directory=/tmp/grub --boot-directory=/tmp/grub \
  --bootloader-id=ab --no-nvram --recheck
sudo efibootmgr -c -d /dev/sda -p 1 -L "AB GRUB" -l '\EFI\ab\grubx64.efi'

The second one is to create a bootloader image:

dd if=/dev/zero of=esp.img count=512 bs=1M
mkfs.vfat -F 32 esp.img
mkdir /tmp/grub
sudo mount -o loop esp.img /tmp/grub
sudo mkdir -p /tmp/grub/EFI/ab
sudo grub-mkimage --format=x86_64-efi --output=/tmp/grub/EFI/ab/grubx64.efi --prefix='(hd0,1)/grub' part_gpt part_msdos chain boot ext2 fat test search search_fs_uuid configfile echo linux normal loadenv
sudo mkdir /tmp/grub/grub

Instead of sda1 and 1 you can use something else, for example sda4 and 4.

Set up the bootloader environment:

sudo grub-editenv /tmp/grub/grub/grubenv set A_OK=0 B_OK=0 A_TRY=0 B_TRY=0 ORDER="A B"

Put the following data into /tmp/grub/grub/grub.cfg:

# set default menuentry (Slot A) and timeout (5s)
default=0
timeout=5
terminal_output console

any_ok=0

set ORDER="A B"
set A_OK=0
set B_OK=0
set A_TRY=0
set B_TRY=0
load_env

# select bootable slot
for SLOT in $ORDER; do
    if [ "$SLOT" == "A" ]; then
        INDEX=0
        OK=$A_OK
        TRY=$A_TRY
        A_TRY=1
    fi
    if [ "$SLOT" == "B" ]; then
        INDEX=1
        OK=$B_OK
        TRY=$B_TRY
        B_TRY=1
    fi
    if [ "$OK" -eq 1 -a "$TRY" -eq 0 ]; then
        default=$INDEX
        any_ok=1
        break
    fi
done

# reset booted flags in case both sides have failed to boot
if [ "$any_ok" -eq 0 ]; then
    if [ "$A_OK" -eq 1 -a "$A_TRY" -eq 1 ]; then
        A_TRY=0
    fi
    if [ "$B_OK" -eq 1 -a "$B_TRY" -eq 1 ]; then
        B_TRY=0
    fi
fi

save_env A_TRY B_TRY

CMDLINE="panic=60 quiet"

menuentry "Slot A (OK=$A_OK TRY=$A_TRY)" {
    linux (hd0,2)/boot/vmlinuz root=/dev/sda2 $CMDLINE rauc.slot=A
    initrd (hd0,2)/boot/initrd.img
}

menuentry "Slot B (OK=$B_OK TRY=$B_TRY)" {
    linux (hd0,3)/boot/vmlinuz root=/dev/sda3 $CMDLINE rauc.slot=B
    initrd (hd0,3)/boot/initrd.img
}

Then execute sudo umount /tmp/grub.

initrd is optional; add it if your OS needs it.

If you created an image, write it to your device:

sudo dd if=esp.img of=/dev/sda1 bs=4M status=progress conv=fsync

Remember that the image must not be larger than the target partition.

Set up the OS

Mount your slot A. It can be either a partition or an image.

Creating an ext4 image:

dd if=/dev/zero of=disk.img count=6144 bs=1M
mkfs.ext4 disk.img

Here is a partition mount example:

mkdir /tmp/slotA
sudo mount /dev/sda2 /tmp/slotA

Here is an image mount example:

mkdir /tmp/slotA
sudo mount -o loop disk.img /tmp/slotA

Create a minimal OS (Ubuntu):

sudo debootstrap --arch=amd64 noble /tmp/slotA

Create the config directories:

sudo mkdir -p /tmp/slotA/etc/rauc /tmp/slotA/etc/grub /tmp/slotA/mnt/rauc

Put the following data into /tmp/slotA/etc/rauc/system.conf:

[system]
compatible=rauc-demo-x86
bootloader=grub
mountprefix=/mnt/rauc
bundle-formats=-plain
grubenv=/etc/grub/grub/grubenv

[keyring]
path=demo.cert.pem

[slot.rootfs.0]
device=/dev/sda2
type=ext4
bootname=A

[slot.rootfs.1]
device=/dev/sda3
type=ext4
bootname=B

Instead of sda2 and sda3 you can use something else.

Add a systemd service to /tmp/slotA/etc/systemd/system/rauc-mark-good.service:

[Unit]
Description=RAUC Good-marking Service
ConditionKernelCommandLine=|rauc.slot
Wants=rauc.service
After=rauc.service dbus.socket
RequiresMountsFor=/etc/grub

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/bin/rauc status mark-good

[Install]
WantedBy=multi-user.target

Generate the keys:

openssl req -x509 -newkey rsa:4096 -nodes -keyout demo.key.pem -out demo.cert.pem -subj "/O=rauc Inc./CN=rauc-demo"

Here you can put your own data and change the key and certificate names.

After that, copy demo.cert.pem:

cp demo.cert.pem /tmp/slotA/etc/rauc/demo.cert.pem

Modify the fstab at /tmp/slotA/etc/fstab:

PARTUUID=766fa522-8195-4568-b4cb-527e95d3a6f0  /home  ext4  defaults,nodev,nosuid  0  2
PARTUUID=0666bffd-d8ec-4475-83d2-dcd0a6688fa0  /etc/grub  vfat  umask=0077  0  0

You can get the PARTUUID values for fstab via sudo blkid. You can also add other partitions, or just use /dev/sdaN instead of PARTUUID:

/dev/sda4  /home  ext4  defaults,nodev,nosuid  0  2
/dev/sda1  /etc/grub  vfat  umask=0077  0  0

Chroot into the slot:

cd /tmp/slotA
sudo mount -t proc /proc proc/
sudo mount -t sysfs /sys sys/
sudo mount -o bind /dev dev/
sudo mount -o bind /run run/
sudo chroot /tmp/slotA /bin/bash

Install the packages:

sudo apt update
sudo apt install grub-common rauc rauc-service

Enable the rauc-mark-good service:

systemctl enable rauc-mark-good.service

Exit the chroot via exit and unmount everything:

sudo umount /tmp/slotA/run /tmp/slotA/dev /tmp/slotA/sys /tmp/slotA/proc
sudo umount /tmp/slotA

If you are doing the initial setup on a live device, clone slot A to slot B. e2image works on the block devices themselves, and both slots must be unmounted, so boot from a live or rescue system if slot A is the rootfs you are currently running from:

sudo e2image -ra -p /dev/sda2 /dev/sda3

The clone also copies the filesystem UUID, so assign a new one to slot B to keep both slots distinguishable:

sudo tune2fs -U random /dev/sda3

Also remember that you can create .img files for the rootfs and the bootloader. After that you can write the images to your live device via dd:

sudo dd if=disk.img of=/dev/sdaN bs=4M status=progress conv=fsync

One-shot boot for testing

# It shows all EFI entries, find the number of "AB GRUB"
sudo efibootmgr
# Put your number instead of 0000
sudo efibootmgr -n 0000

Create update

Use dd to clone the rootfs if you did the initial setup on a live device. Otherwise you already have the .img file.

Chroot into your image. After that you can add, remove or modify files.

Exit the chroot and unmount it.

mkdir update1
mv disk.img update1/disk.img
touch update1/manifest.raucm

Put the following data into update1/manifest.raucm:

[update]
compatible=rauc-demo-x86
version=demo.update1
build=11.08.2026;12:16

[bundle]
format=verity

[image.rootfs]
filename=disk.img

You can write any data in the version and build fields. More information about the data available in the manifest is here.

Also copy demo.cert.pem and demo.key.pem to your folder, and build the bundle:

rauc --cert demo.cert.pem --key demo.key.pem bundle update1/ update1-11.08.2026-1.raucb

After that you will get the update file update1-11.08.2026-1.raucb.

Install update

RAUC supports installing an update not only from a file; you can also put a URL instead of a file path and it will work.

Via rauc

Just execute:

sudo rauc install path/to/generated_file.raucb

Via D-Bus

More information about using the D-Bus API is here.

sudo busctl call de.pengutronix.rauc / de.pengutronix.rauc.Installer InstallBundle sa{sv} "path/to/generated_file.raucb" 0

Table of contents