#!/usr/bin/env bash # Build provisioning initramfs for Raspberry Pi 4 / CM4 (aarch64). # Produces initrd.img (gzip cpio) for TFTP boot (config.txt: initramfs initrd.img followkernel). # # Uses an Alpine Linux aarch64 base with Python 3 so rpi-eeprom-config can run # directly in the initramfs (no chroot into eMMC needed for rescue). # # Requires Docker or Podman with arm64 emulation: # Fedora: sudo dnf install -y qemu-user-static # Debian/Ubuntu: sudo apt install -y qemu-user-static set -e SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" OUTPUT="${1:-$SCRIPT_DIR/initrd.img}" ALPINE_VERSION="3.22" RPI_EEPROM_REPO="https://github.com/raspberrypi/rpi-eeprom" RPI_EEPROM_RAW="https://raw.githubusercontent.com/raspberrypi/rpi-eeprom" # Find container runtime CONTAINER_RUNTIME="" for cmd in podman docker; do if command -v "$cmd" >/dev/null 2>&1; then CONTAINER_RUNTIME="$cmd" break fi done if [ -z "$CONTAINER_RUNTIME" ]; then echo "Error: Docker or Podman is required to build the Alpine-based initramfs." echo "Install one of them and ensure arm64 emulation is available:" echo " Fedora: sudo dnf install -y podman qemu-user-static" echo " Debian/Ubuntu: sudo apt install -y podman qemu-user-static" exit 1 fi echo "Using $CONTAINER_RUNTIME to build Alpine aarch64 initramfs..." # Revision shown on serial so you can confirm the device is running the latest initrd REV=$(date +%Y%m%d-%H%M 2>/dev/null || echo "unknown") if [ -d "$SCRIPT_DIR/../.git" ] || [ -d "$SCRIPT_DIR/../../.git" ]; then REV="${REV}-$(git -C "$SCRIPT_DIR" rev-parse --short HEAD 2>/dev/null || echo 'nogit')" fi CNT_NAME="cm4-initramfs-build-$$" trap "$CONTAINER_RUNTIME rm -f $CNT_NAME >/dev/null 2>&1; true" EXIT # Build the rootfs inside an arm64 Alpine container $CONTAINER_RUNTIME run --name "$CNT_NAME" --platform linux/arm64 \ "alpine:${ALPINE_VERSION}" /bin/sh -c " set -e # Install packages: Python for rpi-eeprom-config, curl for provisioning client, coreutils for dd apk add --no-cache python3 curl busybox coreutils # Download rpi-eeprom tools from GitHub (use curl -sL which follows redirects; busybox wget does not) curl -sL -o /usr/bin/rpi-eeprom-config \ '${RPI_EEPROM_RAW}/master/rpi-eeprom-config' curl -sL -o /usr/bin/rpi-eeprom-update \ '${RPI_EEPROM_RAW}/master/rpi-eeprom-update' curl -sL -o /usr/bin/rpi-eeprom-digest \ '${RPI_EEPROM_RAW}/master/rpi-eeprom-digest' chmod +x /usr/bin/rpi-eeprom-config /usr/bin/rpi-eeprom-update /usr/bin/rpi-eeprom-digest # Download latest stable CM4 (BCM2711) EEPROM firmware mkdir -p /lib/firmware/raspberrypi/bootloader/default # Use GitHub API to find the latest pieeprom-*.bin in firmware-2711/default/ LATEST_FW=\$(curl -sL 'https://api.github.com/repos/raspberrypi/rpi-eeprom/contents/firmware-2711/default' \ | grep -o '\"name\" *: *\"pieeprom-[^\"]*\\.bin\"' | sed 's/\"name\" *: *\"//;s/\"//' | sort | tail -1) if [ -n \"\$LATEST_FW\" ]; then echo \"Downloading EEPROM firmware: \$LATEST_FW\" curl -sL -o /lib/firmware/raspberrypi/bootloader/default/pieeprom.bin \ \"${RPI_EEPROM_RAW}/master/firmware-2711/default/\$LATEST_FW\" else echo 'WARNING: Could not determine latest firmware; rescue-eeprom.sh may not work' fi # Create required directories mkdir -p /proc /sys /dev/pts /mnt /tmp /run /usr/share/udhcpc /etc/default # Clean up unnecessary files to reduce size rm -rf /var/cache/apk/* /usr/share/man /usr/share/doc /usr/include \ /usr/lib/python*/test /usr/lib/python*/unittest /usr/lib/python*/idlelib \ /usr/lib/python*/tkinter /usr/lib/python*/ensurepip /usr/lib/python*/__pycache__/test* \ /usr/lib/python*/lib2to3 /usr/lib/python*/distutils \ /usr/share/terminfo/[a-s]* /usr/share/terminfo/[u-z]* echo 'Alpine rootfs ready' " echo "Container build done. Copying scripts into container..." # Copy our scripts into the container $CONTAINER_RUNTIME cp "$SCRIPT_DIR/init" "$CNT_NAME:/init" $CONTAINER_RUNTIME cp "$SCRIPT_DIR/provisioning-client.sh" "$CNT_NAME:/provisioning-client.sh" $CONTAINER_RUNTIME cp "$SCRIPT_DIR/rescue-eeprom.sh" "$CNT_NAME:/rescue-eeprom.sh" $CONTAINER_RUNTIME cp "$SCRIPT_DIR/udhcpc.script" "$CNT_NAME:/usr/share/udhcpc/default.script" # Write revision.txt echo "$REV" | $CONTAINER_RUNTIME cp /dev/stdin "$CNT_NAME:/revision.txt" 2>/dev/null || \ (echo "$REV" > /tmp/cm4-rev-$$.txt && $CONTAINER_RUNTIME cp /tmp/cm4-rev-$$.txt "$CNT_NAME:/revision.txt" && rm -f /tmp/cm4-rev-$$.txt) # Set permissions $CONTAINER_RUNTIME start "$CNT_NAME" >/dev/null 2>&1 || true $CONTAINER_RUNTIME exec "$CNT_NAME" chmod +x /init /provisioning-client.sh /rescue-eeprom.sh /usr/share/udhcpc/default.script 2>/dev/null || \ echo "Note: could not chmod in stopped container; permissions set by cp" # Export container filesystem and create cpio archive echo "Exporting filesystem and building initrd.img..." $CONTAINER_RUNTIME export "$CNT_NAME" | gzip -1 > /tmp/cm4-rootfs-$$.tar.gz BUILD_DIR=$(mktemp -d) trap "$CONTAINER_RUNTIME rm -f $CNT_NAME >/dev/null 2>&1; rm -rf $BUILD_DIR /tmp/cm4-rootfs-$$.tar.gz; true" EXIT cd "$BUILD_DIR" tar xzf /tmp/cm4-rootfs-$$.tar.gz rm -f /tmp/cm4-rootfs-$$.tar.gz # Remove container metadata that shouldn't be in initramfs rm -rf .dockerenv .containerenv # Ensure init is executable and at the root chmod +x init provisioning-client.sh rescue-eeprom.sh usr/share/udhcpc/default.script 2>/dev/null || true # Build cpio (gzip) echo "Building cpio..." find . -print0 | cpio -o -H newc -0 2>/dev/null | gzip -9 > "$OUTPUT" SIZE=$(stat -c%s "$OUTPUT" 2>/dev/null || stat -f%z "$OUTPUT" 2>/dev/null || echo "?") echo "" echo "Written: $OUTPUT ($SIZE bytes, $(( ${SIZE:-0} / 1048576 )) MB)" echo "" echo "Next: copy initrd.img to TFTP root (e.g. /srv/tftpboot on LXC) and in config.txt add:" echo " initramfs initrd.img followkernel" echo "Default provisioning server: http://10.20.50.1:5000 (override with kernel cmdline: provisioning_server=http://...)"