# 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 ```bash # 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 ``` ```bash # 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 | ## Step-by-Step: Quick Build (Recommended First Build) 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. ```bash # 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. ```bash # 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): ```bash ./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: ```bash MIXER_WIFI_SSID="MyNetwork" \ MIXER_WIFI_PASS="secret123" \ ./build/build.sh --skip-kernel ``` ### Custom Hostname ```bash MIXER_HOSTNAME="stage-mixer" ./build/build.sh --skip-kernel ``` ### Custom API Key for CI ```bash MIXER_API_KEY="my-pre-shared-key" ./build/build.sh --skip-kernel ``` ### Custom OS Base Image ```bash RPIOS_URL="https://downloads.raspberrypi.com/raspios_lite_arm64/images/..." \ ./build/build.sh ``` ### Version Tagging ```bash export MIXER_VERSION="$(git describe --tags --always)" ./build/build.sh --skip-kernel ``` ## Cleaning Up ```bash # 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. ```bash ./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. ```bash ./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 | ```bash ./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 ```bash # 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 ```bash # 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](https://www.raspberrypi.com/software/) or [balenaEtcher](https://www.balena.io/etcher/): 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: ```bash ssh pi@pi-mixer.local passwd ``` ### Service Management ```bash # 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: ```bash 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: ```yaml - 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): ```bash # 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](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](research/base-os-decision.md) for the full analysis.