From 73f9950337034e1330b55cfc7487433599b890da Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 22 Mar 2026 20:17:04 +0000 Subject: [PATCH 1/2] Initial plan From 09e663fcdd08d60084f15d515400842e7cc97c28 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 22 Mar 2026 20:27:42 +0000 Subject: [PATCH 2/2] feat: add Raspberry Pi arm64 raspbian support with its own bake target Co-authored-by: dseif0x <11018429+dseif0x@users.noreply.github.com> Agent-Logs-Url: https://github.com/dseif0x/os-docker/sessions/e4d2ebd7-5a36-4121-8e30-108234027823 --- Dockerfile.disk-image | 23 +++----- build-image.sh | 133 +++++++++++++++++++++++++++++------------- docker-bake.hcl | 29 +++++++++ raspbian/Dockerfile | 33 +++++++++++ 4 files changed, 163 insertions(+), 55 deletions(-) create mode 100644 raspbian/Dockerfile diff --git a/Dockerfile.disk-image b/Dockerfile.disk-image index 3ff393d..fdc0c82 100644 --- a/Dockerfile.disk-image +++ b/Dockerfile.disk-image @@ -3,25 +3,21 @@ FROM debian:bookworm-slim AS packager ARG TARGETARCH +ARG DISTRO=linux ARG DEBIAN_FRONTEND=noninteractive RUN apt-get update \ - && case "${TARGETARCH}" in \ - amd64) \ - apt-get install -y --no-install-recommends \ - grub-efi-amd64-bin \ - ;; \ - arm64) \ - apt-get install -y --no-install-recommends \ - grub-efi-arm64-bin \ - ;; \ - *) echo "Unsupported TARGETARCH: ${TARGETARCH}" && exit 1 ;; \ - esac \ + && if [ "${DISTRO}" != "raspbian" ]; then \ + case "${TARGETARCH}" in \ + amd64) apt-get install -y --no-install-recommends grub-efi-amd64-bin ;; \ + arm64) apt-get install -y --no-install-recommends grub-efi-arm64-bin ;; \ + *) echo "Unsupported TARGETARCH: ${TARGETARCH}" && exit 1 ;; \ + esac && \ + apt-get install -y --no-install-recommends grub2-common; \ + fi \ && apt-get install -y --no-install-recommends \ # Disk tools gdisk dosfstools e2fsprogs kpartx \ - # GRUB tools (grub-install, grub-mkimage) — modules come from the arch-specific -bin pkgs above - grub2-common \ # Filesystem / process tools mount util-linux coreutils rsync \ && apt-get clean && rm -rf /var/lib/apt/lists/* @@ -36,7 +32,6 @@ RUN chmod +x /build-image.sh RUN mkdir -p /output ARG EFI_SIZE=512M -ARG DISTRO=linux RUN --security=insecure /build-image.sh FROM scratch diff --git a/build-image.sh b/build-image.sh index aab05a5..b2a1e65 100644 --- a/build-image.sh +++ b/build-image.sh @@ -1,11 +1,14 @@ #!/usr/bin/env bash # build-image.sh — runs inside the packager container (needs security.insecure) # Produces /output/disk.img: a bootable GPT disk with: -# partition 1 — 512 MiB EFI System Partition (FAT32, GRUB EFI) +# partition 1 — EFI/boot partition (FAT32) +# alpine/debian: GRUB EFI (mounted at /boot/efi) +# raspbian: RPi firmware + kernel (mounted at /boot/firmware) # partition 2 — remainder Linux root (ext4, rootfs) # # Respects: # TARGETARCH — amd64 | arm64 (set by BuildKit / bake, drives GRUB target) +# DISTRO — alpine | debian | raspbian (selects boot method) # IMG_SIZE — e.g. 4G (default), 8G, etc. set -euo pipefail @@ -18,7 +21,13 @@ ROOTFS=/rootfs MNT=/mnt/disk TARGETARCH=${TARGETARCH:-amd64} -# ── Resolve arch-specific GRUB EFI target ──────────────────────────────────── +# ── Resolve boot mount point and bootloader method ──────────────────────────── +case "${DISTRO}" in + raspbian) BOOT_MNT="${MNT}/boot/firmware" ;; + *) BOOT_MNT="${MNT}/boot/efi" ;; +esac + +# ── Resolve arch-specific GRUB EFI target (used for non-raspbian only) ──────── case "${TARGETARCH}" in amd64) GRUB_EFI_TARGET="x86_64-efi" ;; arm64) GRUB_EFI_TARGET="arm64-efi" ;; @@ -28,7 +37,11 @@ case "${TARGETARCH}" in ;; esac -echo ">>> Building disk image for ${TARGETARCH} (GRUB EFI target: ${GRUB_EFI_TARGET})" +if [ "${DISTRO}" = "raspbian" ]; then + echo ">>> Building disk image for ${TARGETARCH} (Raspberry Pi firmware boot)" +else + echo ">>> Building disk image for ${TARGETARCH} (GRUB EFI target: ${GRUB_EFI_TARGET})" +fi # ── Create sparse image ─────────────────────────────────────────────────────── echo ">>> Creating ${IMG_SIZE} disk image..." @@ -55,7 +68,7 @@ ROOT="/dev/mapper/${LOOPNAME}p2" cleanup() { echo ">>> Cleanup..." - umount "${MNT}/boot/efi" 2>/dev/null || true + umount "${BOOT_MNT}" 2>/dev/null || true umount "${MNT}/dev" 2>/dev/null || true umount "${MNT}/proc" 2>/dev/null || true umount "${MNT}/sys" 2>/dev/null || true @@ -74,8 +87,8 @@ mkfs.ext4 -L root "${ROOT}" echo ">>> Mounting..." mkdir -p "${MNT}" mount "${ROOT}" "${MNT}" -mkdir -p "${MNT}/boot/efi" -mount "${ESP}" "${MNT}/boot/efi" +mkdir -p "${BOOT_MNT}" +mount "${ESP}" "${BOOT_MNT}" # ── Copy rootfs ─────────────────────────────────────────────────────────────── echo ">>> Copying rootfs..." @@ -93,44 +106,80 @@ mount --bind /dev "${MNT}/dev" # ── Write /etc/fstab ───────────────────────────────────────────────────────── ROOT_UUID=$(blkid -s UUID -o value "${ROOT}") ESP_UUID=$(blkid -s UUID -o value "${ESP}") +BOOT_FSTAB_MNT="${BOOT_MNT#"${MNT}"}" # absolute path for fstab: /boot/efi or /boot/firmware + +case "${DISTRO}" in + raspbian) BOOT_FSTAB_OPTS="defaults" ;; + *) BOOT_FSTAB_OPTS="umask=0077" ;; +esac cat > "${MNT}/etc/fstab" < -UUID=${ROOT_UUID} / ext4 errors=remount-ro 0 1 -UUID=${ESP_UUID} /boot/efi vfat umask=0077 0 2 +# +UUID=${ROOT_UUID} / ext4 errors=remount-ro 0 1 +UUID=${ESP_UUID} ${BOOT_FSTAB_MNT} vfat ${BOOT_FSTAB_OPTS} 0 2 FSTAB -# ── Install GRUB ───────────────────────────────────────────────────────────── -# Run grub-install from the packager (not chroot) so it can find its modules -# under /usr/lib/grub/. Point it at the mounted rootfs via --boot-directory -# and --efi-directory. -echo ">>> Installing GRUB EFI (${GRUB_EFI_TARGET})..." -grub-install \ - --target="${GRUB_EFI_TARGET}" \ - --efi-directory="${MNT}/boot/efi" \ - --boot-directory="${MNT}/boot" \ - --bootloader-id=debian \ - --no-nvram \ - --removable - - -# ── Generate grub.cfg ───────────────────────────────────────────────────────── -# Write grub.cfg directly rather than using update-grub: inside the chroot -# grub-probe sees the build-time device (/dev/mapper/loopNpX) and emits that -# as root=, which is meaningless on the target machine. Using the UUID we -# already have guarantees the correct root= on every boot. -echo ">>> Generating grub.cfg..." -# find works for both Debian (vmlinuz-6.x, initrd.img-6.x) -# and Alpine (vmlinuz-lts, initramfs-lts) -KERNEL=$(find "${MNT}/boot" -maxdepth 1 -name 'vmlinuz*' ! -name '*.old' | sort -V | tail -1) -INITRD=$(find "${MNT}/boot" -maxdepth 1 \( -name 'initrd*' -o -name 'initramfs*' \) ! -name '*.old' | sort -V | tail -1) -[ -n "${KERNEL}" ] || { echo "ERROR: no kernel found in ${MNT}/boot"; exit 1; } -[ -n "${INITRD}" ] || { echo "ERROR: no initrd/initramfs found in ${MNT}/boot"; exit 1; } -KERNEL="${KERNEL#"${MNT}"}" # strip mount prefix → /boot/vmlinuz-… -INITRD="${INITRD#"${MNT}"}" - -mkdir -p "${MNT}/boot/grub" -cat > "${MNT}/boot/grub/grub.cfg" <>> Setting up Raspberry Pi firmware boot..." + KERNEL=$(find "${MNT}/boot" -maxdepth 1 -name 'vmlinuz*' ! -name '*.old' | sort -V | tail -1) + INITRD=$(find "${MNT}/boot" -maxdepth 1 \( -name 'initrd*' -o -name 'initramfs*' \) ! -name '*.old' | sort -V | tail -1) + [ -n "${KERNEL}" ] || { echo "ERROR: no kernel found in ${MNT}/boot"; exit 1; } + [ -n "${INITRD}" ] || { echo "ERROR: no initrd found in ${MNT}/boot"; exit 1; } + + cp "${KERNEL}" "${BOOT_MNT}/kernel8.img" + cp "${INITRD}" "${BOOT_MNT}/initrd.img" + + # config.txt — Raspberry Pi boot configuration + cat > "${BOOT_MNT}/config.txt" <<'RPICFG' +[all] +arm_64bit=1 +kernel=kernel8.img +initramfs initrd.img followkernel +RPICFG + + # cmdline.txt — kernel command line (root identified by PARTUUID) + ROOT_PARTUUID=$(blkid -s PARTUUID -o value "${ROOT}") + printf 'console=serial0,115200 console=tty1 root=PARTUUID=%s rootfstype=ext4 fsck.repair=yes rootwait\n' \ + "${ROOT_PARTUUID}" > "${BOOT_MNT}/cmdline.txt" + +else + # ── Standard GRUB EFI boot ─────────────────────────────────────────────── + # Run grub-install from the packager (not chroot) so it can find its modules + # under /usr/lib/grub/. Point it at the mounted rootfs via --boot-directory + # and --efi-directory. + echo ">>> Installing GRUB EFI (${GRUB_EFI_TARGET})..." + grub-install \ + --target="${GRUB_EFI_TARGET}" \ + --efi-directory="${BOOT_MNT}" \ + --boot-directory="${MNT}/boot" \ + --bootloader-id=debian \ + --no-nvram \ + --removable + + + # ── Generate grub.cfg ────────────────────────────────────────────────────── + # Write grub.cfg directly rather than using update-grub: inside the chroot + # grub-probe sees the build-time device (/dev/mapper/loopNpX) and emits that + # as root=, which is meaningless on the target machine. Using the UUID we + # already have guarantees the correct root= on every boot. + echo ">>> Generating grub.cfg..." + # find works for both Debian (vmlinuz-6.x, initrd.img-6.x) + # and Alpine (vmlinuz-lts, initramfs-lts) + KERNEL=$(find "${MNT}/boot" -maxdepth 1 -name 'vmlinuz*' ! -name '*.old' | sort -V | tail -1) + INITRD=$(find "${MNT}/boot" -maxdepth 1 \( -name 'initrd*' -o -name 'initramfs*' \) ! -name '*.old' | sort -V | tail -1) + [ -n "${KERNEL}" ] || { echo "ERROR: no kernel found in ${MNT}/boot"; exit 1; } + [ -n "${INITRD}" ] || { echo "ERROR: no initrd/initramfs found in ${MNT}/boot"; exit 1; } + KERNEL="${KERNEL#"${MNT}"}" # strip mount prefix → /boot/vmlinuz-… + INITRD="${INITRD#"${MNT}"}" + + mkdir -p "${MNT}/boot/grub" + cat > "${MNT}/boot/grub/grub.cfg" <>> Shrinking image..." # Unmount everything before resizing the filesystem -umount "${MNT}/boot/efi" 2>/dev/null || true +umount "${BOOT_MNT}" 2>/dev/null || true umount "${MNT}/dev" 2>/dev/null || true umount "${MNT}/proc" 2>/dev/null || true umount "${MNT}/sys" 2>/dev/null || true diff --git a/docker-bake.hcl b/docker-bake.hcl index cdea896..16b7dd5 100644 --- a/docker-bake.hcl +++ b/docker-bake.hcl @@ -53,3 +53,32 @@ target "disk-image" { output = ["./output/${distro}"] } + +# ── Raspberry Pi (arm64 only) ───────────────────────────────────────────────── + +target "rootfs-raspbian" { + context = "." + dockerfile = "raspbian/Dockerfile" + platforms = ["linux/arm64"] + output = [] +} + +target "disk-image-raspbian" { + platforms = ["linux/arm64"] + context = "." + dockerfile = "Dockerfile.disk-image" + + contexts = { + "rootfs" = "target:rootfs-raspbian" + } + + entitlements = ["security.insecure"] + + args = { + IMG_SIZE = "4G" + EFI_SIZE = "256M" + DISTRO = "raspbian" + } + + output = ["./output/raspbian"] +} diff --git a/raspbian/Dockerfile b/raspbian/Dockerfile new file mode 100644 index 0000000..dd83f58 --- /dev/null +++ b/raspbian/Dockerfile @@ -0,0 +1,33 @@ +FROM debian:bookworm-slim + +ARG DEBIAN_FRONTEND=noninteractive + +# Add the Raspberry Pi Foundation repository (provides raspberrypi-bootloader) +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + curl ca-certificates gnupg \ + && curl -fsSL https://archive.raspberrypi.com/debian/raspberrypi.gpg.key \ + | gpg --dearmor -o /usr/share/keyrings/raspberrypi-archive-keyring.gpg \ + && printf 'deb [arch=arm64 signed-by=/usr/share/keyrings/raspberrypi-archive-keyring.gpg] https://archive.raspberrypi.com/debian/ bookworm main\n' \ + > /etc/apt/sources.list.d/raspi.list \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + linux-image-arm64 \ + raspberrypi-bootloader \ + systemd systemd-sysv dbus \ + bash coreutils util-linux mount udev \ + iproute2 iputils-ping \ + curl ca-certificates \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +RUN systemctl enable getty@tty1.service +RUN systemctl enable systemd-networkd.service + +RUN printf '[Match]\nType=ether\n\n[Network]\nDHCP=yes\n' \ + > /etc/systemd/network/20-eth.network + +RUN echo "root:root" | chpasswd