No description
Find a file
2026-08-28 00:20:37 +02:00
buildroot@313414b92c init 2026-08-28 00:05:16 +02:00
external init 2026-08-28 00:05:16 +02:00
openssl-ca init 2026-08-28 00:05:16 +02:00
.gitignore init 2026-08-28 00:05:16 +02:00
.gitmodules init 2026-08-28 00:05:16 +02:00
build.log init 2026-08-28 00:05:16 +02:00
openssl-ca.sh init 2026-08-28 00:05:16 +02:00
pi4-buildroot-rauc-handoff.md init 2026-08-28 00:05:16 +02:00
README.md Added submodule init 2026-08-28 00:20:37 +02:00

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 (bmaptool or also dd and xzcat should 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:

  1. 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 create tailscale/authkey containing just the key.
    • Put the card back in and boot. The tailscale-autologin.service picks 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.
  2. Wi-Fi credentials, if/when you want the Wi-Fi-fallback path used instead of/alongside wired Ethernet. /etc/NetworkManager/system-connections is a symlink to /data/NetworkManager/system-connections. Put a standard .nmconnection file 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

  1. Make your change, rebuild with an explicit version:
    cd output && make VERSION=1.1.0
    
  2. Publish output/images/update.raucb as a release asset on git.weiler.rocks/rpiaudio/rpios, tagged exactly 1.1.0. The tag name is what update-check.sh compares against. The repo/release must stay public, or update-check.sh needs a token access token.
  3. Within an hour (the timer's OnUnitActiveSec=1h), every device polls .../releases/latest, sees the new tag, and rauc installs it straight from the release asset URL onto its inactive slot.
  4. At the next 03:00 window (scheduled-reboot.timer), if an update was installed, the device reboots into it.
  5. The new slot has 3 boot attempts (BOOT_<slot>_LEFT, set in u-boot.ush) to get confirm-boot.service to see mpv running stably for 30s and call rauc status mark-good. If that never happens U-Boot falls back to the last good slot.