Beacon CM4 — Buildroot + RAUC
Buildroot BR2_EXTERNAL for a Raspberry Pi CM4 with A/B OTA updates via RAUC.
Partition layout
| # | Label | Size | Content |
|---|---|---|---|
| p1 | boot_a | 64 MiB | FAT32 — firmware + U-Boot (slot A) |
| p2 | boot_b | 64 MiB | FAT32 — firmware + U-Boot (slot B) |
| p3 | data | 256 MiB | ext4 — persistent data (/data) |
| p4 | (extended) | — | — |
| p5 | rootfs0 | 250 MiB | ext4 — rootfs slot A |
| p6 | rootfs1 | 250 MiB | ext4 — rootfs slot B |
| p7 | upload | 256 MiB | ext4 — staging area for bundles (/upload) |
U-Boot reads BOOT_ORDER/BOOT_x_LEFT env vars from eMMC and selects the active slot before loading the kernel. RAUC uses boot-mbr-switch to toggle between slots.
One-time setup
1. Generate RAUC signing certificates
Run once from the beacon-buildroot/ directory:
cd ~/repos/buildroot-beacon/beacon-buildroot
./openssl-ca.sh "Beacon" "Beacon RAUC CA"
This creates openssl-ca/dev/ with:
openssl-ca/dev/
ca.cert.pem ← keyring installed into target /etc/rauc/keyring.pem
development-1.cert.pem ← signing cert (build host)
private/
development-1.key.pem ← signing key (build host, keep secret)
Do not regenerate the CA once devices are flashed — bundles signed with a new CA will be rejected by devices that have the old keyring.
2. Initial full build
cd ~/repos/buildroot-beacon
make -C rpi-buildroot-fork \
O=$(pwd)/output \
BR2_EXTERNAL=$(pwd)/beacon-buildroot \
beacon_cm4_rauc_defconfig
make -C rpi-buildroot-fork \
O=$(pwd)/output \
BR2_EXTERNAL=$(pwd)/beacon-buildroot \
-j$(nproc)
Output artifacts in output/images/:
| File | Purpose |
|---|---|
sdcard.img.xz |
Full eMMC image for initial flash |
sdcard.img.bmap |
Block map for fast flash with bmaptool |
rootfs.raucb |
OTA bundle — rootfs only |
update.raucb |
OTA bundle — bootfs + rootfs (full system) |
3. Initial flash (EMMC_DISABLE jumper bridged)
./beacon-buildroot/scripts/flash-cm4.sh
Remove the jumper and power-cycle after the script completes.
Creating an OTA update
What triggers a new bundle
Any source change that results in a different rootfs.ext4 or boot.vfat will produce a new bundle on the next build. Typical triggers:
- Package version bump / new package in defconfig
- File added/changed under
rootfs-overlay/ post-build.shchanges- Kernel or U-Boot update
Build the update
Incremental build — only changed packages and the rootfs/image stage are rebuilt:
cd ~/repos/buildroot-beacon
# Optional: set a human-readable version string
export VERSION="1.2.0"
make -C rpi-buildroot-fork \
O=$(pwd)/output \
BR2_EXTERNAL=$(pwd)/beacon-buildroot \
-j$(nproc)
post-image.sh runs automatically at the end and:
- Builds
boot.vfatfrom U-Boot + firmware blobs - Creates
rootfs.raucb(rootfs-only bundle) - Creates
update.raucb(full bootfs+rootfs bundle) - Signs both bundles with
development-1.key.pem - Assembles
sdcard.img.xz+.bmap
Bundle contents
rootfs.raucb manifest (format=verity):
[update]
compatible=beacon-cm4
version=<VERSION>
[bundle]
format=verity
[image.rootfs]
filename=rootfs.ext4
update.raucb additionally contains [image.bootloader] → boot.vfat.
Signing details
Signing is done by the host rauc binary during post-image.sh:
rauc bundle \
--cert openssl-ca/dev/development-1.cert.pem \
--key openssl-ca/dev/private/development-1.key.pem \
--keyring openssl-ca/dev/ca.cert.pem \
<bundle-dir>/ <output>.raucb
The target verifies the bundle signature against /etc/rauc/keyring.pem
(= ca.cert.pem installed during build by post-build.sh).
To inspect a bundle without installing it:
output/host/bin/rauc \
--keyring beacon-buildroot/openssl-ca/dev/ca.cert.pem \
info output/images/rootfs.raucb
Deploying the update to the CM4
Find the CM4's IP
The CM4 gets a DHCP address on eth0 (changes on each reboot):
# By MAC address:
ip neigh show dev enp0s31f6 | grep e4:5f:01:e9:13:96
Transfer the bundle
Dropbear has no sftp-server — standard scp does not work.
Use stdin pipe instead:
CM4=10.11.0.xx # replace with actual IP
sshpass -p beacon ssh user@$CM4 \
'sudo tee /upload/rootfs.raucb > /dev/null' \
< output/images/rootfs.raucb
~51 MiB transfers in ~5 s on LAN.
Install
sshpass -p beacon ssh user@$CM4 'rauc install /upload/rootfs.raucb'
Expected output:
0% Installing
20% Checking bundle done.
40% Determining target install group done.
46% Checking slot rootfs.1 done.
99% Copying image to rootfs.1 done.
100% Installing done.
Installing `/upload/rootfs.raucb` succeeded
RAUC automatically selects the inactive slot as the target.
Reboot into the new slot
sshpass -p beacon ssh user@$CM4 'rauc status && sudo reboot'
rauc status should show Activated: rootfs.1 (B) before the reboot.
Confirm and mark-good
After reboot the system boots into the new slot in trial mode
(U-Boot decrements BOOT_x_LEFT). You must mark it good or it will
roll back on the next reboot.
# Find new IP (DHCP address changes):
CM4_NEW=$(ip neigh show dev enp0s31f6 | awk '/e4:5f:01:e9:13:96/{print $1}')
sshpass -p beacon ssh user@$CM4_NEW 'rauc status mark-good && rauc status'
Expected final rauc status:
Booted from: rootfs.1 (B)
Activated: rootfs.1 (B)
x [rootfs.1] boot status: good ← currently running, committed
o [rootfs.0] boot status: good ← fallback
Rollback behaviour
If mark-good is not called after a reboot, U-Boot decrements
BOOT_x_LEFT. After 3 failed attempts it switches back to the previous
slot automatically — no manual intervention needed.
To force an immediate rollback:
# On the CM4:
sudo fw_setenv BOOT_ORDER "A B" # or "B A" depending on current slot
sudo reboot
Rescue partition
Short GPIO4 (pin 7) to GND (pin 9) on the 40-pin header during
power-on. U-Boot detects this and boots the read-only rescue rootfs from
/dev/mmcblk0p2.
Reference
| Path | Purpose |
|---|---|
configs/beacon_cm4_rauc_defconfig |
Buildroot defconfig |
board/beacon-cm4/genimage.cfg |
Partition layout |
board/beacon-cm4/post-image.sh |
Bundle creation + signing |
board/beacon-cm4/post-build.sh |
Target rootfs customisation |
board/beacon-cm4/rootfs-overlay/etc/rauc/system.conf |
RAUC slot config |
openssl-ca/dev/ |
Signing certificates (generated once) |
scripts/flash-cm4.sh |
Automated initial flash script |