From dba1cb93c11ab2a602fd41b647ed1a3ba2f85452 Mon Sep 17 00:00:00 2001 From: Aiden McClelland Date: Thu, 12 Mar 2026 11:12:04 -0600 Subject: [PATCH] feat: raspberry pi U-Boot + GPT + btrfs boot chain MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Switch Raspberry Pi builds from proprietary firmware direct-boot to a firmware → U-Boot → GRUB → kernel chain using GPT partitioning: - GPT partition layout with fixed UUIDs matching os_install: firmware (128MB), ESP (100MB), boot (2GB FAT32), root (btrfs) - U-Boot as the kernel in config.txt, chainloading GRUB EFI - Pi-specific GRUB config overrides (console, USB quirks, cgroup) - Btrfs root with shrink-to-minimum for image compression - init_resize.sh updated for GPT (sgdisk -e) and btrfs resize - Removed os-partitions from config.yaml (now derived from fstab) --- build/dpkg-deps/raspberrypi.depends | 3 +- build/image-recipe/Dockerfile | 2 + build/image-recipe/build.sh | 125 +++++++++++++----- build/image-recipe/raspberrypi/img/etc/fstab | 6 +- .../usr/lib/startos/scripts/init_resize.sh | 30 ++--- .../raspberrypi/squashfs/boot/cmdline.txt | 1 - .../raspberrypi/squashfs/boot/config.sh | 16 +-- .../raspberrypi/squashfs/boot/config.txt | 6 +- .../etc/default/grub.d/raspberrypi.cfg | 4 + .../squashfs/etc/startos/config.yaml | 3 - 10 files changed, 130 insertions(+), 66 deletions(-) delete mode 100644 build/image-recipe/raspberrypi/squashfs/boot/cmdline.txt create mode 100644 build/image-recipe/raspberrypi/squashfs/etc/default/grub.d/raspberrypi.cfg diff --git a/build/dpkg-deps/raspberrypi.depends b/build/dpkg-deps/raspberrypi.depends index b8f74d108..9066caffd 100644 --- a/build/dpkg-deps/raspberrypi.depends +++ b/build/dpkg-deps/raspberrypi.depends @@ -1,5 +1,6 @@ -- grub-efi ++ gdisk + parted ++ u-boot-rpi + raspberrypi-net-mods + raspberrypi-sys-mods + raspi-config diff --git a/build/image-recipe/Dockerfile b/build/image-recipe/Dockerfile index c53627214..13d1a80b0 100644 --- a/build/image-recipe/Dockerfile +++ b/build/image-recipe/Dockerfile @@ -23,6 +23,8 @@ RUN apt-get update && \ squashfs-tools \ rsync \ b3sum \ + btrfs-progs \ + gdisk \ dpkg-dev diff --git a/build/image-recipe/build.sh b/build/image-recipe/build.sh index 0b3024286..bc6fa43e7 100755 --- a/build/image-recipe/build.sh +++ b/build/image-recipe/build.sh @@ -132,6 +132,10 @@ ff02::1 ip6-allnodes ff02::2 ip6-allrouters EOT +# Installer marker file (used by installed GRUB to detect the live USB) +mkdir -p config/includes.binary +touch config/includes.binary/.startos-installer + if [ "${IB_TARGET_PLATFORM}" = "raspberrypi" ]; then mkdir -p config/includes.chroot git clone --depth=1 --branch=stable https://github.com/raspberrypi/rpi-firmware.git config/includes.chroot/boot @@ -322,9 +326,10 @@ fi if [ "${IB_TARGET_PLATFORM}" = "raspberrypi" ]; then ln -sf /usr/bin/pi-beep /usr/local/bin/beep - KERNEL_VERSION=${RPI_KERNEL_VERSION} sh /boot/config.sh > /boot/config.txt + sh /boot/config.sh > /boot/config.txt mkinitramfs -c gzip -o /boot/initrd.img-${RPI_KERNEL_VERSION}-rpi-v8 ${RPI_KERNEL_VERSION}-rpi-v8 mkinitramfs -c gzip -o /boot/initrd.img-${RPI_KERNEL_VERSION}-rpi-2712 ${RPI_KERNEL_VERSION}-rpi-2712 + cp /usr/lib/u-boot/rpi_arm64/u-boot.bin /boot/u-boot.bin fi useradd --shell /bin/bash -G startos -m start9 @@ -390,38 +395,65 @@ if [ "${IMAGE_TYPE}" = iso ]; then elif [ "${IMAGE_TYPE}" = img ]; then SECTOR_LEN=512 - BOOT_START=$((1024 * 1024)) # 1MiB - BOOT_LEN=$((512 * 1024 * 1024)) # 512MiB + FW_START=$((1024 * 1024)) # 1MiB (sector 2048) — Pi-specific + FW_LEN=$((128 * 1024 * 1024)) # 128MiB (Pi firmware + U-Boot + DTBs) + FW_END=$((FW_START + FW_LEN - 1)) + ESP_START=$((FW_END + 1)) # 100MB EFI System Partition (matches os_install) + ESP_LEN=$((100 * 1024 * 1024)) + ESP_END=$((ESP_START + ESP_LEN - 1)) + BOOT_START=$((ESP_END + 1)) # 2GB /boot (matches os_install) + BOOT_LEN=$((2 * 1024 * 1024 * 1024)) BOOT_END=$((BOOT_START + BOOT_LEN - 1)) ROOT_START=$((BOOT_END + 1)) ROOT_LEN=$((MAX_IMG_LEN - ROOT_START)) - ROOT_END=$((MAX_IMG_LEN - 1)) + + # Fixed GPT partition UUIDs (deterministic, based on old MBR disk ID cb15ae4d) + FW_UUID=cb15ae4d-0001-4000-8000-000000000001 + ESP_UUID=cb15ae4d-0002-4000-8000-000000000002 + BOOT_UUID=cb15ae4d-0003-4000-8000-000000000003 + ROOT_UUID=cb15ae4d-0004-4000-8000-000000000004 TARGET_NAME=$prep_results_dir/${IMAGE_BASENAME}.img truncate -s $MAX_IMG_LEN $TARGET_NAME sfdisk $TARGET_NAME <<-EOF - label: dos - label-id: 0xcb15ae4d - unit: sectors - sector-size: 512 + label: gpt - ${TARGET_NAME}1 : start=$((BOOT_START / SECTOR_LEN)), size=$((BOOT_LEN / SECTOR_LEN)), type=c, bootable - ${TARGET_NAME}2 : start=$((ROOT_START / SECTOR_LEN)), size=$((ROOT_LEN / SECTOR_LEN)), type=83 + ${TARGET_NAME}1 : start=$((FW_START / SECTOR_LEN)), size=$((FW_LEN / SECTOR_LEN)), type=EBD0A0A2-B9E5-4433-87C0-68B6B72699C7, uuid=${FW_UUID}, name="firmware" + ${TARGET_NAME}2 : start=$((ESP_START / SECTOR_LEN)), size=$((ESP_LEN / SECTOR_LEN)), type=C12A7328-F81F-11D2-BA4B-00A0C93EC93B, uuid=${ESP_UUID}, name="efi" + ${TARGET_NAME}3 : start=$((BOOT_START / SECTOR_LEN)), size=$((BOOT_LEN / SECTOR_LEN)), type=0FC63DAF-8483-4772-8E79-3D69D8477DE4, uuid=${BOOT_UUID}, name="boot" + ${TARGET_NAME}4 : start=$((ROOT_START / SECTOR_LEN)), size=$((ROOT_LEN / SECTOR_LEN)), type=B921B045-1DF0-41C3-AF44-4C6F280D3FAE, uuid=${ROOT_UUID}, name="root" EOF + FW_DEV=$(losetup --show -f --offset $FW_START --sizelimit $FW_LEN $TARGET_NAME) + ESP_DEV=$(losetup --show -f --offset $ESP_START --sizelimit $ESP_LEN $TARGET_NAME) BOOT_DEV=$(losetup --show -f --offset $BOOT_START --sizelimit $BOOT_LEN $TARGET_NAME) ROOT_DEV=$(losetup --show -f --offset $ROOT_START --sizelimit $ROOT_LEN $TARGET_NAME) - mkfs.vfat -F32 $BOOT_DEV - mkfs.ext4 $ROOT_DEV + mkfs.vfat -F32 -n firmware $FW_DEV + mkfs.vfat -F32 -n efi $ESP_DEV + mkfs.vfat -F32 -n boot $BOOT_DEV + mkfs.btrfs -f -L rootfs $ROOT_DEV TMPDIR=$(mktemp -d) - mkdir -p $TMPDIR/boot $TMPDIR/root - mount $ROOT_DEV $TMPDIR/root + # Extract boot files from squashfs to staging area + BOOT_STAGING=$(mktemp -d) + unsquashfs -n -f -d $BOOT_STAGING $prep_results_dir/binary/live/filesystem.squashfs boot + + # Mount partitions + mkdir -p $TMPDIR/firmware $TMPDIR/efi $TMPDIR/boot $TMPDIR/root + mount $FW_DEV $TMPDIR/firmware + mount $ESP_DEV $TMPDIR/efi mount $BOOT_DEV $TMPDIR/boot - unsquashfs -n -f -d $TMPDIR $prep_results_dir/binary/live/filesystem.squashfs boot + mount $ROOT_DEV $TMPDIR/root + + # Split boot files: firmware to Part 1, kernels/initramfs to Part 3 (/boot) + cp -a $BOOT_STAGING/boot/. $TMPDIR/firmware/ + for f in $TMPDIR/firmware/vmlinuz-* $TMPDIR/firmware/initrd.img-* $TMPDIR/firmware/System.map-* $TMPDIR/firmware/config-*; do + [ -e "$f" ] && mv "$f" $TMPDIR/boot/ + done + rm -rf $BOOT_STAGING mkdir $TMPDIR/root/images $TMPDIR/root/config B3SUM=$(b3sum $prep_results_dir/binary/live/filesystem.squashfs | head -c 16) @@ -434,40 +466,73 @@ elif [ "${IMAGE_TYPE}" = img ]; then mount -t overlay -o lowerdir=$TMPDIR/lower,workdir=$TMPDIR/root/config/work,upperdir=$TMPDIR/root/config/overlay overlay $TMPDIR/next if [ "${IB_TARGET_PLATFORM}" = "raspberrypi" ]; then - sed -i 's| boot=startos| boot=startos init=/usr/lib/startos/scripts/init_resize\.sh|' $TMPDIR/boot/cmdline.txt rsync -a $SOURCE_DIR/raspberrypi/img/ $TMPDIR/next/ + + # Install GRUB: ESP at /boot/efi (Part 2), /boot (Part 3) + mkdir -p $TMPDIR/next/boot $TMPDIR/next/boot/efi $TMPDIR/next/boot/firmware \ + $TMPDIR/next/dev $TMPDIR/next/proc $TMPDIR/next/sys $TMPDIR/next/media/startos/root + mount --bind $TMPDIR/boot $TMPDIR/next/boot + mount --bind $TMPDIR/efi $TMPDIR/next/boot/efi + mount --bind $TMPDIR/firmware $TMPDIR/next/boot/firmware + mount --bind /dev $TMPDIR/next/dev + mount --bind /proc $TMPDIR/next/proc + mount --bind /sys $TMPDIR/next/sys + mount --bind $TMPDIR/root $TMPDIR/next/media/startos/root + + chroot $TMPDIR/next grub-install --target=arm64-efi --removable --efi-directory=/boot/efi --boot-directory=/boot --no-nvram + chroot $TMPDIR/next update-grub + + umount $TMPDIR/next/media/startos/root + umount $TMPDIR/next/sys + umount $TMPDIR/next/proc + umount $TMPDIR/next/dev + umount $TMPDIR/next/boot/firmware + umount $TMPDIR/next/boot/efi + umount $TMPDIR/next/boot + + # Fix root= in grub.cfg: update-grub sees loop devices, but the + # real device uses a fixed GPT PARTUUID for root (Part 4). + sed -i "s|root=[^ ]*|root=PARTUUID=${ROOT_UUID}|g" $TMPDIR/boot/grub/grub.cfg + + # Inject first-boot resize script into GRUB config + sed -i 's| boot=startos| boot=startos init=/usr/lib/startos/scripts/init_resize\.sh|' $TMPDIR/boot/grub/grub.cfg fi umount $TMPDIR/next umount $TMPDIR/lower + umount $TMPDIR/firmware + umount $TMPDIR/efi umount $TMPDIR/boot umount $TMPDIR/root - - e2fsck -fy $ROOT_DEV - resize2fs -M $ROOT_DEV - - BLOCK_COUNT=$(dumpe2fs -h $ROOT_DEV | awk '/^Block count:/ { print $3 }') - BLOCK_SIZE=$(dumpe2fs -h $ROOT_DEV | awk '/^Block size:/ { print $3 }') - ROOT_LEN=$((BLOCK_COUNT * BLOCK_SIZE)) + # Shrink btrfs to minimum size + SHRINK_MNT=$(mktemp -d) + mount $ROOT_DEV $SHRINK_MNT + btrfs filesystem resize min $SHRINK_MNT + umount $SHRINK_MNT + rmdir $SHRINK_MNT + ROOT_LEN=$(btrfs inspect-internal dump-super $ROOT_DEV | awk '/^total_bytes/ {print $2}') losetup -d $ROOT_DEV losetup -d $BOOT_DEV + losetup -d $ESP_DEV + losetup -d $FW_DEV - # Recreate partition 2 with the new size using sfdisk + # Recreate partition table with shrunk root sfdisk $TARGET_NAME <<-EOF - label: dos - label-id: 0xcb15ae4d - unit: sectors - sector-size: 512 + label: gpt - ${TARGET_NAME}1 : start=$((BOOT_START / SECTOR_LEN)), size=$((BOOT_LEN / SECTOR_LEN)), type=c, bootable - ${TARGET_NAME}2 : start=$((ROOT_START / SECTOR_LEN)), size=$((ROOT_LEN / SECTOR_LEN)), type=83 + ${TARGET_NAME}1 : start=$((FW_START / SECTOR_LEN)), size=$((FW_LEN / SECTOR_LEN)), type=EBD0A0A2-B9E5-4433-87C0-68B6B72699C7, uuid=${FW_UUID}, name="firmware" + ${TARGET_NAME}2 : start=$((ESP_START / SECTOR_LEN)), size=$((ESP_LEN / SECTOR_LEN)), type=C12A7328-F81F-11D2-BA4B-00A0C93EC93B, uuid=${ESP_UUID}, name="efi" + ${TARGET_NAME}3 : start=$((BOOT_START / SECTOR_LEN)), size=$((BOOT_LEN / SECTOR_LEN)), type=0FC63DAF-8483-4772-8E79-3D69D8477DE4, uuid=${BOOT_UUID}, name="boot" + ${TARGET_NAME}4 : start=$((ROOT_START / SECTOR_LEN)), size=$((ROOT_LEN / SECTOR_LEN)), type=B921B045-1DF0-41C3-AF44-4C6F280D3FAE, uuid=${ROOT_UUID}, name="root" EOF TARGET_SIZE=$((ROOT_START + ROOT_LEN)) truncate -s $TARGET_SIZE $TARGET_NAME + # Move backup GPT to new end of disk after truncation + sgdisk -e $TARGET_NAME mv $TARGET_NAME $RESULTS_DIR/$IMAGE_BASENAME.img diff --git a/build/image-recipe/raspberrypi/img/etc/fstab b/build/image-recipe/raspberrypi/img/etc/fstab index 5f5164232..ece1fb4c7 100644 --- a/build/image-recipe/raspberrypi/img/etc/fstab +++ b/build/image-recipe/raspberrypi/img/etc/fstab @@ -1,2 +1,4 @@ -/dev/mmcblk0p1 /boot vfat umask=0077 0 2 -/dev/mmcblk0p2 / ext4 defaults 0 1 +PARTUUID=cb15ae4d-0001-4000-8000-000000000001 /boot/firmware vfat umask=0077 0 2 +PARTUUID=cb15ae4d-0002-4000-8000-000000000002 /boot/efi vfat umask=0077 0 1 +PARTUUID=cb15ae4d-0003-4000-8000-000000000003 /boot vfat umask=0077 0 2 +PARTUUID=cb15ae4d-0004-4000-8000-000000000004 / btrfs defaults 0 1 diff --git a/build/image-recipe/raspberrypi/img/usr/lib/startos/scripts/init_resize.sh b/build/image-recipe/raspberrypi/img/usr/lib/startos/scripts/init_resize.sh index 1fdca1c83..faa796c7b 100755 --- a/build/image-recipe/raspberrypi/img/usr/lib/startos/scripts/init_resize.sh +++ b/build/image-recipe/raspberrypi/img/usr/lib/startos/scripts/init_resize.sh @@ -12,15 +12,16 @@ get_variables () { BOOT_DEV_NAME=$(echo /sys/block/*/"${BOOT_PART_NAME}" | cut -d "/" -f 4) BOOT_PART_NUM=$(cat "/sys/block/${BOOT_DEV_NAME}/${BOOT_PART_NAME}/partition") - OLD_DISKID=$(fdisk -l "$ROOT_DEV" | sed -n 's/Disk identifier: 0x\([^ ]*\)/\1/p') - ROOT_DEV_SIZE=$(cat "/sys/block/${ROOT_DEV_NAME}/size") - if [ "$ROOT_DEV_SIZE" -le 67108864 ]; then - TARGET_END=$((ROOT_DEV_SIZE - 1)) + # GPT backup header/entries occupy last 33 sectors + USABLE_END=$((ROOT_DEV_SIZE - 34)) + + if [ "$USABLE_END" -le 67108864 ]; then + TARGET_END=$USABLE_END else TARGET_END=$((33554432 - 1)) DATA_PART_START=33554432 - DATA_PART_END=$((ROOT_DEV_SIZE - 1)) + DATA_PART_END=$USABLE_END fi PARTITION_TABLE=$(parted -m "$ROOT_DEV" unit s print | tr -d 's') @@ -61,33 +62,24 @@ main () { return 1 fi -# if [ "$ROOT_PART_END" -eq "$TARGET_END" ]; then -# reboot_pi -# fi - if ! echo Yes | parted -m --align=optimal "$ROOT_DEV" ---pretend-input-tty u s resizepart "$ROOT_PART_NUM" "$TARGET_END" ; then FAIL_REASON="Root partition resize failed" return 1 fi if [ -n "$DATA_PART_START" ]; then - if ! parted -ms --align=optimal "$ROOT_DEV" u s mkpart primary "$DATA_PART_START" "$DATA_PART_END"; then + if ! parted -ms --align=optimal "$ROOT_DEV" u s mkpart data "$DATA_PART_START" "$DATA_PART_END"; then FAIL_REASON="Data partition creation failed" return 1 fi fi - ( - echo x - echo i - echo "0xcb15ae4d" - echo r - echo w - ) | fdisk $ROOT_DEV + # Fix GPT backup header to reflect new partition layout + sgdisk -e "$ROOT_DEV" 2>/dev/null || true mount / -o remount,rw - resize2fs $ROOT_PART_DEV + btrfs filesystem resize max /media/startos/root if ! systemd-machine-id-setup --root=/media/startos/config/overlay/; then FAIL_REASON="systemd-machine-id-setup failed" @@ -111,7 +103,7 @@ mount / -o remount,ro beep if main; then - sed -i 's| init=/usr/lib/startos/scripts/init_resize\.sh||' /boot/cmdline.txt + sed -i 's| init=/usr/lib/startos/scripts/init_resize\.sh||' /boot/grub/grub.cfg echo "Resized root filesystem. Rebooting in 5 seconds..." sleep 5 else diff --git a/build/image-recipe/raspberrypi/squashfs/boot/cmdline.txt b/build/image-recipe/raspberrypi/squashfs/boot/cmdline.txt deleted file mode 100644 index f10c50da5..000000000 --- a/build/image-recipe/raspberrypi/squashfs/boot/cmdline.txt +++ /dev/null @@ -1 +0,0 @@ -usb-storage.quirks=152d:0562:u,14cd:121c:u,0781:cfcb:u console=serial0,115200 console=tty1 root=PARTUUID=cb15ae4d-02 rootfstype=ext4 fsck.repair=yes rootwait cgroup_enable=cpuset cgroup_memory=1 cgroup_enable=memory boot=startos \ No newline at end of file diff --git a/build/image-recipe/raspberrypi/squashfs/boot/config.sh b/build/image-recipe/raspberrypi/squashfs/boot/config.sh index 1c74bc1b2..d23120bef 100644 --- a/build/image-recipe/raspberrypi/squashfs/boot/config.sh +++ b/build/image-recipe/raspberrypi/squashfs/boot/config.sh @@ -27,20 +27,18 @@ disable_overscan=1 # (e.g. for USB device mode) or if USB support is not required. otg_mode=1 -[all] - [pi4] # Run as fast as firmware / board allows arm_boost=1 -kernel=vmlinuz-${KERNEL_VERSION}-rpi-v8 -initramfs initrd.img-${KERNEL_VERSION}-rpi-v8 followkernel - -[pi5] -kernel=vmlinuz-${KERNEL_VERSION}-rpi-2712 -initramfs initrd.img-${KERNEL_VERSION}-rpi-2712 followkernel [all] gpu_mem=16 dtoverlay=pwm-2chan,disable-bt -EOF \ No newline at end of file +# Enable UART for U-Boot and serial console +enable_uart=1 + +# Load U-Boot as the bootloader (GRUB is chainloaded from U-Boot) +kernel=u-boot.bin + +EOF diff --git a/build/image-recipe/raspberrypi/squashfs/boot/config.txt b/build/image-recipe/raspberrypi/squashfs/boot/config.txt index 4e1962a65..5bf25925d 100644 --- a/build/image-recipe/raspberrypi/squashfs/boot/config.txt +++ b/build/image-recipe/raspberrypi/squashfs/boot/config.txt @@ -84,4 +84,8 @@ arm_boost=1 gpu_mem=16 dtoverlay=pwm-2chan,disable-bt -auto_initramfs=1 \ No newline at end of file +# Enable UART for U-Boot and serial console +enable_uart=1 + +# Load U-Boot as the bootloader (GRUB is chainloaded from U-Boot) +kernel=u-boot.bin \ No newline at end of file diff --git a/build/image-recipe/raspberrypi/squashfs/etc/default/grub.d/raspberrypi.cfg b/build/image-recipe/raspberrypi/squashfs/etc/default/grub.d/raspberrypi.cfg new file mode 100644 index 000000000..0dc217b8b --- /dev/null +++ b/build/image-recipe/raspberrypi/squashfs/etc/default/grub.d/raspberrypi.cfg @@ -0,0 +1,4 @@ +# Raspberry Pi-specific GRUB overrides +# Overrides GRUB_CMDLINE_LINUX from /etc/default/grub with Pi-specific +# console devices and hardware quirks. +GRUB_CMDLINE_LINUX="boot=startos console=serial0,115200 console=tty1 usb-storage.quirks=152d:0562:u,14cd:121c:u,0781:cfcb:u cgroup_enable=cpuset cgroup_memory=1 cgroup_enable=memory" diff --git a/build/image-recipe/raspberrypi/squashfs/etc/startos/config.yaml b/build/image-recipe/raspberrypi/squashfs/etc/startos/config.yaml index 7c81ad513..a7d1a5eae 100644 --- a/build/image-recipe/raspberrypi/squashfs/etc/startos/config.yaml +++ b/build/image-recipe/raspberrypi/squashfs/etc/startos/config.yaml @@ -1,6 +1,3 @@ -os-partitions: - boot: /dev/mmcblk0p1 - root: /dev/mmcblk0p2 ethernet-interface: end0 wifi-interface: wlan0 disable-encryption: true