No description
- Shell 100%
| buildroot@313414b92c | ||
| external | ||
| openssl-ca | ||
| .gitignore | ||
| .gitmodules | ||
| build.log | ||
| openssl-ca.sh | ||
| pi4-buildroot-rauc-handoff.md | ||
| README.md | ||
Bettenried Pi — audio appliance
Single-purpose remote audio player on a Raspberry Pi 4: SD card, read-only rootfs, A/B updates over HTTPS via RAUC, automatic rollback on boot failure via U-Boot, reachable only over Tailscale/SSH for debugging. No screen, no local input.
Built with Buildroot 2026.05 (git submodule) + U-Boot + RAUC, adapted from cdsteinkuehler/br2rauc.
Repo layout
buildroot/ upstream Buildroot, git submodule pinned to 2026.05
external/ BR2_EXTERNAL tree (this is the actual project)
configs/bettenried_pi4_64_defconfig
board/bettenried/
genimage.cfg partition layout
u-boot.ush boot script source (A/B slot selection)
u-boot.fragment U-Boot kernel config fragment
config_4_64bit.txt RPi firmware config.txt
post-build.sh runs after packages+overlay, before imaging
post-image.sh builds sdcard.img and update.raucb
rootfs-overlay/ files copied verbatim onto the target rootfs
linux.fragment
openssl-ca/ RAUC signing CA — generated, gitignored except certs
openssl-ca.sh generates the CA above
Prerequisites
- Linux build host, ~20GB free disk, the usual Buildroot host packages (gcc, make, cpio, unzip, rsync, bc, python3, wget or curl i think).
- A Raspberry Pi 4, a 16GB+ SD card, and a way to write it (
bmaptoolor alsoddandxzcatshould be fine) - A Tailscale account/tailnet, and an auth key generated from the admin console (Settings → Keys). A reusable, short-expiry key is easiest for provisioning more than one sd-card a single-use key is fine for one device.
Building
# `buildroot/` is a git submodule and comes empty from `git clone`
git submodule update --init --recursive
# One-time: generate the RAUC bundle-signing CA
# back up openssl-ca/dev/private/ somewhere safe and NEVER commit it
./openssl-ca.sh "Bettenried" "RAUC CA"
# Configure
make -C buildroot O=../output BR2_EXTERNAL=../external bettenried_pi4_64_defconfig
# Build, ccache can be enabled via `make menuconfig`: Build options → Enable compiler cache
cd output
make
# build-timestamp version string:
make VERSION=1.0.0
Output, in output/images/:
sdcard.img[.xz]— flash this to a blank SD card. Both the A and B slots start out identical.update.raucb— the same rootfs as a signed RAUC bundle.Can be publish as a Forgejo release asset for OTA updates.
Flashing
sudo bmaptool copy output/images/sdcard.img.xz /dev/sdX
# or, without bmaptool:
xzcat output/images/sdcard.img.xz | sudo dd of=/dev/sdX bs=4M status=progress conv=fsync
First-boot provisioning
The image ships with the SSH public key already baked in.
The /data partition needs two additional things:
- Tailscale auth key without this the device has no network path with which you can reach it remotely
Options:
- Boot the Pi with a wired Ethernet connection.
- SSH isn't possible yet without Tailscale, so instead: pull the SD card back out.
- Mount its 4th partition (
data, ext4) on your PC, and createtailscale/authkeycontaining just the key. - Put the card back in and boot. The
tailscale-autologin.servicepicks it up automatically within a few seconds of boot. - Alternatively, mount the data partition and drop the file in before ever booting the SDcard the first time.
- Wi-Fi credentials, if/when you want the Wi-Fi-fallback path used
instead of/alongside wired Ethernet.
/etc/NetworkManager/system-connectionsis a symlink to/data/NetworkManager/system-connections. Put a standard.nmconnectionfile there (mode 600), either by mounting the data partition directly, or once you have Tailscale SSH access:nmcli connection add type wifi con-name home-wifi ifname wlan0 \ ssid "YOUR_SSID" wifi-sec.key-mgmt wpa-psk wifi-sec.psk "YOUR_PASSWORD"
Cutting an OTA update
- Make your change, rebuild with an explicit version:
cd output && make VERSION=1.1.0 - Publish
output/images/update.raucbas a release asset ongit.weiler.rocks/rpiaudio/rpios, tagged exactly1.1.0. The tag name is whatupdate-check.shcompares against. The repo/release must stay public, orupdate-check.shneeds a token access token. - Within an hour (the timer's
OnUnitActiveSec=1h), every device polls.../releases/latest, sees the new tag, andrauc installs it straight from the release asset URL onto its inactive slot. - At the next
03:00window (scheduled-reboot.timer), if an update was installed, the device reboots into it. - The new slot has 3 boot attempts (
BOOT_<slot>_LEFT, set inu-boot.ush) to getconfirm-boot.serviceto see mpv running stably for 30s and callrauc status mark-good. If that never happens U-Boot falls back to the last good slot.