Files
shawn e450189858
Lint & Validate / lint (push) Has been cancelled
Docs: Full USB/NVMe boot coverage across all documentation
- build-guide.md: Boot medium options table, USB/NVMe flash instructions,
  EEPROM boot order setup, first-boot wizard boot config step
- user-manual.md: Required hardware lists all 3 boot targets, EEPROM
  bootstrap instructions, NVMe HAT recommendation for live use
- hardware-compatibility.md: New storage/boot media section with NVMe
  HAT compatibility table (official, Pimoroni, Geekworm)
- Obsidian wiki: Build section with all 3 targets + EEPROM config
- developer-guide.md: Added (from task completion)
2026-05-19 22:58:49 -04:00

13 KiB

Build Guide — Disk Image Builder (SD / USB / NVMe)

This guide walks through building a ready-to-flash Raspberry Pi disk image containing the full audio mixer stack with a PREEMPT_RT kernel. Supports SD card, USB SSD, and NVMe HAT boot targets.

Prerequisites

Host System Requirements

  • OS: Any modern Linux distribution (x86_64) — Ubuntu 22.04+, Debian 12+, Fedora 38+
  • CPU: 4+ cores recommended (cross-compilation is parallel)
  • RAM: 4GB+ (8GB recommended for full PREEMPT_RT kernel build)
  • Disk: 15GB free space (build artifacts + downloaded OS image)
  • Architecture: x86_64 host for QEMU user-static chroot emulation
  • macOS is supported via a Linux VM or Docker container

Install Build Dependencies

# Ubuntu / Debian
sudo apt update
sudo apt install -y \
    wget xz-utils \
    dosfstools e2fsprogs \
    qemu-user-static \
    parted \
    git

# For PREEMPT_RT kernel cross-compilation (optional)
sudo apt install -y \
    gcc-aarch64-linux-gnu \
    build-essential \
    flex bison \
    libssl-dev \
    bc \
    rsync

# Verify QEMU binary is registered
sudo update-binfmts --enable qemu-aarch64
# Fedora
sudo dnf install -y wget xz dosfstools e2fsprogs qemu-user-static parted git

Build Modes

The build script supports three modes, controlled by command-line flags:

Command Description Time Requires
./build/build.sh --skip-kernel Quick build, stock RPi kernel 10-15 min Basic deps only
./build/build.sh Full build with PREEMPT_RT kernel 30-45 min + gcc-aarch64 + kernel deps
./build/build.sh --kernel-only Build only the RT kernel .deb 10-15 min + gcc-aarch64 + kernel deps

This builds the mixer image using the stock Raspberry Pi OS kernel — fastest way to get a working image for testing. No cross-compilation toolchain needed.

# 1. Clone the repository
git clone https://github.com/your-org/rpi-audio-mixer.git
cd rpi-audio-mixer

# 2. Run the quick build
./build/build.sh --skip-kernel

# 3. The compressed image lands here:
ls -lh build/out/rpi-audio-mixer-*.img.xz

What happens during the build:

  1. Downloads Raspberry Pi OS Lite (64-bit, Bookworm) base image (~1.1 GB)
  2. Extracts and resizes the image (default 8 GB total)
  3. Mounts the image partitions via loopback
  4. Chroots into the ARM64 filesystem using QEMU user-static
  5. Installs audio packages (JACK2, Carla, ALSA tools, Python deps)
  6. Copies the mixer source code and systemd service files
  7. Configures real-time audio limits, CPU governor, udev rules
  8. Sets up first-boot wizard
  9. Unmounts and compresses the final image

Step-by-Step: Full PREEMPT_RT Build

The full build includes a custom PREEMPT_RT kernel cross-compiled for aarch64 on your x86_64 host. This is what you want for production use.

# 1. Ensure cross-compilation toolchain is installed
sudo apt install -y gcc-aarch64-linux-gnu build-essential flex bison libssl-dev bc

# 2. Run the full build
./build/build.sh

# This will:
#   a. Cross-compile linux-rpi-6.12.y with PREEMPT_RT patch
#   b. Package the kernel as a .deb
#   c. Build the SD card image with the RT kernel installed
#   d. Configure boot/config.txt to use the RT kernel

# 3. Output
ls -lh build/out/rpi-audio-mixer-*.img.xz

Kernel Cross-Compilation Details

The build script:

  • Clones raspberrypi/linux at branch rpi-6.12.y
  • Applies the PREEMPT_RT patch for 6.12
  • Uses make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- bcm2711_defconfig
  • Configures: CONFIG_PREEMPT_RT=y, CONFIG_HZ=1000
  • Builds with make -j$(nproc) for parallel compilation
  • Packages into a .deb using make deb-pkg

Build Customization

Image Size

The default image is 8 GB. Adjust with --image-size (in MB):

./build/build.sh --skip-kernel --image-size 16384   # 16 GB image
./build/build.sh --skip-kernel --image-size 32768   # 32 GB image

Pre-configure WiFi

Set these environment variables before building:

MIXER_WIFI_SSID="MyNetwork" \
MIXER_WIFI_PASS="secret123" \
./build/build.sh --skip-kernel

Custom Hostname

MIXER_HOSTNAME="stage-mixer" ./build/build.sh --skip-kernel

Custom API Key for CI

MIXER_API_KEY="my-pre-shared-key" ./build/build.sh --skip-kernel

Custom OS Base Image

RPIOS_URL="https://downloads.raspberrypi.com/raspios_lite_arm64/images/..." \
./build/build.sh

Version Tagging

export MIXER_VERSION="$(git describe --tags --always)"
./build/build.sh --skip-kernel

Cleaning Up

# Remove all build artifacts
./build/build.sh --clean

# This deletes:
#   build/work/       (mounted image, chroot state, temporary files)
#   build/downloads/  (cached OS image downloads)
#   build/out/        (built images)

# To preserve downloads (they're cached for faster rebuilds):
rm -rf build/work/ build/out/

Image Contents

Partition layout:
├── /boot (FAT32, 256 MB)
│   ├── kernel8.img             Stock 64-bit kernel (fallback)
│   ├── kernel8-rt.img          PREEMPT_RT kernel (default if built)
│   ├── config.txt              GPU, boot, kernel selection
│   ├── cmdline.txt             Kernel parameters (isolcpus, threadirqs, etc.)
│   └── *.dtb, overlays/        Device tree blobs
│
├── / (ext4, ~4 GB used of 6 GB)
│   ├── /opt/rpi-mixer/         Mixer Python source + virtualenv
│   ├── /etc/systemd/system/    Service unit files
│   │   ├── cpu-performance.service
│   │   ├── jackd.service
│   │   ├── mixer-api.service
│   │   ├── mixer-ui.service
│   │   └── mixer-firstboot.service
│   ├── /etc/security/limits.d/99-audio.conf
│   ├── /etc/modprobe.d/alsa-usb.conf
│   └── /home/pi/               Default user home
│
└── /data (ext4, remaining space)
    ├── sessions/               Saved mixer session JSON files
    ├── recordings/             Multi-track WAV recordings
    ├── presets/                Plugin presets
    └── logs/                   System and audio logs

Boot Medium Options

The same disk image works with three boot targets. Choose during build:

Target --target Recommended For Boot Order (EEPROM)
SD Card sd (default) Development, testing SD → Restart (0xf1)
USB SSD usb Portable use, more reliable than SD USB → SD → Restart (0xf41)
NVMe HAT nvme Production / Live use (no USB bus contention) NVMe → USB → SD → Restart (0xf614)

SD Card (Default)

The standard target. Simple and cheap. Works with any A2-class SD card. Limited write speed (~90 MB/s sequential) and wear on heavy recording.

./build/build.sh  # or --target sd (same thing)

USB SSD

Boot from a USB 3.0 SSD via any USB port. Much faster and more durable than SD. But: Shares the USB bus with audio interfaces — can cause xruns under heavy I/O.

./build/build.sh --target usb

The kernel includes CONFIG_BLK_DEV_NVME as a module when building with --target nvme. For USB target, standard USB mass storage drivers are already present in the stock kernel.

NVMe HAT

The recommended target for production/live use. Uses dedicated PCIe lanes — zero USB bus contention with audio interfaces. Requires a storage HAT:

HAT Interface M.2 Size Notes
Official RPi NVMe Base PCIe Gen 2 x1 2230/2242 Best compatibility
Pimoroni NVMe Base PCIe Gen 2 x1 2230/2242 Well-documented, same as official
Geekworm X1001 PCIe Gen 2 x1 2230 Low profile
Geekworm X1002 PCIe Gen 2 x1 2242/2280 Full length option
./build/build.sh --target nvme

💡 Tip: Buy a 128GB or 256GB NVMe SSD. They're cheap (~$15-25) and dramatically more reliable than SD cards for live recording.

Build Time Comparison

Target Build Time Notes
SD ~30-45 min Stock kernel + config
USB ~30-45 min Same as SD, different output docs
NVMe ~31-46 min +1 min for NVMe driver compilation

Flashing to SD Card

Linux

# Decompress the image
xz -d build/out/rpi-audio-mixer-*.img.xz

# Identify your SD card
lsblk
# Look for /dev/sdb or /dev/mmcblk0 (NOT your system drive!)

# Flash the image
sudo dd if=build/out/rpi-audio-mixer-*.img \
        of=/dev/sdX \
        bs=4M \
        status=progress \
        conv=fsync

# Sync and eject
sync
sudo eject /dev/sdX

macOS

# Decompress
xz -d build/out/rpi-audio-mixer-*.img.xz

# Find the SD card
diskutil list
# Look for /dev/diskX (external, physical)

# Unmount
diskutil unmountDisk /dev/diskX

# Flash (use rdisk for faster writes)
sudo dd if=build/out/rpi-audio-mixer-*.img \
        of=/dev/rdiskX \
        bs=4m \
        status=progress

# Eject
diskutil eject /dev/diskX

Windows

Use Raspberry Pi Imager or balenaEtcher:

  1. Decompress the .img.xz file with 7-Zip or WinRAR
  2. Open Raspberry Pi Imager → "Choose OS" → "Use custom"
  3. Select the .img file
  4. Choose your SD card and flash

First Boot

Initial Setup

  1. Insert the boot media (SD card, USB SSD, or NVMe) into a Raspberry Pi 4B
  2. Connect your USB audio interface
  3. (Optional) Connect HDMI touchscreen + Ethernet
  4. Power on

USB/NVMe boot note: If booting from USB SSD or NVMe, you'll need to configure the EEPROM boot order first. Either:

  • Boot from SD card once to run the setup wizard (it has an EEPROM config step)
  • Or configure manually: sudo rpi-eeprom-config --edit
    • USB: BOOT_ORDER=0xf41
    • NVMe: BOOT_ORDER=0xf614

The first-boot setup wizard will appear on the HDMI display (or serial console). It walks through:

  1. Audio interface detection and selection
  2. WiFi network configuration
  3. Hostname setting
  4. API key generation (for web UI access)
  5. JACK buffer size and latency preferences
  6. Boot target configuration (USB/NVMe EEPROM setup — if accessible)

After the wizard completes, the system reboots into normal operation. The wizard auto-disables itself — it only runs once.

Default Access

Service URL / Command Default Credentials
Web UI http://pi-mixer.local:8080 API key from wizard
SSH ssh pi@pi-mixer.local Password: raspberry
Touch UI HDMI display (auto-starts)

Important: Change the default password immediately:

ssh pi@pi-mixer.local
passwd

Service Management

# Check all mixer services
systemctl status jackd mixer-api mixer-ui

# View logs
journalctl -u mixer-api -f
journalctl -u jackd -f

# Restart services (must follow order: jackd → mixer-api → mixer-ui)
sudo systemctl stop mixer-ui mixer-api jackd
sudo systemctl start jackd
sleep 2
sudo systemctl start mixer-api mixer-ui

CI / Automated Builds

Build without a Raspberry Pi (CI-friendly) on any x86_64 host:

export MIXER_VERSION="$(git describe --tags --always)"
export MIXER_HOSTNAME="ci-mixer"
export MIXER_API_KEY="ci-test-key-12345"

./build/build.sh --skip-kernel

Example GitHub Actions / Gitea CI workflow:

- name: Build SD card image
  run: |
    sudo apt install -y wget xz-utils dosfstools e2fsprogs qemu-user-static
    ./build/build.sh --skip-kernel

- name: Upload artifact
  uses: actions/upload-artifact@v4
  with:
    name: sd-card-image
    path: build/out/rpi-audio-mixer-*.img.xz

Kernel-Only Build

When you only need a new PREEMPT_RT kernel (e.g., to upgrade an existing image without rebuilding everything):

# Build only the kernel .deb package
./build/build.sh --kernel-only

# The .deb lands in build/out/
ls build/out/linux-image-*.deb

# Install on the RPi:
# scp build/out/linux-image-*.deb pi@pi-mixer.local:/tmp/
# ssh pi@pi-mixer.local
# sudo dpkg -i /tmp/linux-image-*.deb
# sudo reboot

Troubleshooting Build Issues

Problem Solution
qemu-aarch64-static not found sudo apt install qemu-user-static && sudo update-binfmts --enable qemu-aarch64
apt fails in chroot DNS not resolving — check host /etc/resolv.conf is accessible
No space left on device Increase image size: --image-size 16384
kernel build fails Install missing deps: sudo apt install gcc-aarch64-linux-gnu flex bison libssl-dev bc
loop device busy Run sudo losetup -D to detach stale loop devices
permission denied Build must run with passwordless sudo or as root

For runtime issues after flashing, see docs/troubleshooting.md.

Architecture Decision

This project uses Raspberry Pi OS Lite (64-bit, Bookworm) + post-install configuration rather than Buildroot or Yocto. Rationale:

  • Full Debian package ecosystem for rapid audio stack iteration
  • apt install for JACK, Carla, debugging tools — no image rebuilds needed
  • Standard systemd-based system — familiar and well-documented
  • PREEMPT_RT kernel built via cross-compilation on x86 host
  • Buildroot/Yocto can be revisited for production appliance images

See docs/research/base-os-decision.md for the full analysis.