
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