#!/bin/bash set -ex DISTRO="${DISTRO:-ubuntu2604}" IMG_SIZE="${IMG_SIZE:-12000}" SKIP_CHROOT="${SKIP_CHROOT:-false}" STAGING="/tmp/build-staging" ROOT_LABEL="${DISTRO}" EFI_LABEL="boot" CHROOT="/build/chroot" IMG="/output/ps5-${DISTRO}.img" if [ "$SKIP_CHROOT" = "true" ] && [ -d "$CHROOT/bin" ]; then echo "=== Reusing cached $DISTRO rootfs ===" else echo "=== Building $DISTRO rootfs ===" # --- Stage files for distrobuilder's copy generators --- rm -rf "$STAGING" mkdir -p "$STAGING/debs" cp /repo/distros/shared/zz-update-boot "$STAGING/" # Generate per-distro fstab with partition labels cat < "$STAGING/fstab" # /etc/fstab: static file system information. # # LABEL=$ROOT_LABEL / ext4 defaults 0 1 LABEL=$EFI_LABEL /boot/efi vfat defaults 0 1 EOF cp /repo/distros/${DISTRO}/grow-rootfs "$STAGING/" cp /repo/distros/${DISTRO}/nm-dns.conf "$STAGING/" 2>/dev/null || true case "$DISTRO" in ubuntu*) cp /repo/distros/${DISTRO}/grow-rootfs.service "$STAGING/" cp /kernel-debs/*.deb "$STAGING/debs/" ;; alpine) cp /repo/distros/alpine/grow-rootfs.openrc "$STAGING/" ;; arch) cp /repo/distros/arch/grow-rootfs.service "$STAGING/" cp /repo/distros/arch/first-boot-setup "$STAGING/" mkdir -p "$STAGING/pkgs" cp /kernel-debs/*.pkg.tar.zst "$STAGING/pkgs/" ;; esac # --- Build rootfs --- rm -rf "$CHROOT"/* "$CHROOT"/.[!.]* 2>/dev/null || true YAML="/repo/distros/${DISTRO}/image.yaml" distrobuilder build-dir "$YAML" "$CHROOT" --with-post-files --cache-dir /build/cache --cleanup=false fi # --- Post-distrobuilder fixups --- case "$DISTRO" in ubuntu*) rm -f "$CHROOT/etc/resolv.conf" ln -sf /run/systemd/resolve/stub-resolv.conf "$CHROOT/etc/resolv.conf" ;; esac # --- Alpine kernel gap: no kernel installed via image.yaml --- # Extract kernel from .deb, copy modules + bzImage, then chroot to run mkinitfs if [ "$DISTRO" = "alpine" ]; then echo "=== Alpine: installing kernel from .deb artifacts ===" ALPINE_STAGING="/tmp/alpine-kernel-staging" rm -rf "$ALPINE_STAGING" mkdir -p "$ALPINE_STAGING" for deb in /kernel-debs/linux-image-*.deb; do [ -f "$deb" ] || continue dpkg-deb -x "$deb" "$ALPINE_STAGING" done KVER=$(ls -1 "$ALPINE_STAGING/lib/modules" 2>/dev/null | head -1) if [ -n "$KVER" ]; then # Resolve the real modules path (Alpine may use usr-merge: /lib -> usr/lib) if [ -L "$CHROOT/lib" ]; then MODDIR="$CHROOT/usr/lib/modules" else MODDIR="$CHROOT/lib/modules" fi mkdir -p "$MODDIR" rm -rf "$MODDIR/$KVER" cp -a "$ALPINE_STAGING/lib/modules/$KVER" "$MODDIR/" mkdir -p "$CHROOT/boot" cp "$ALPINE_STAGING/boot/vmlinuz-$KVER" "$CHROOT/boot/vmlinuz-$KVER" echo ">> Alpine: modules copied to $MODDIR/$KVER" fi rm -rf "$ALPINE_STAGING" if [ -n "$KVER" ]; then chroot "$CHROOT" depmod -a "$KVER" 2>/dev/null || true mount --bind /dev "$CHROOT/dev" mount --bind /proc "$CHROOT/proc" mount --bind /sys "$CHROOT/sys" chroot "$CHROOT" mkinitfs -k "$KVER" -o "/boot/initrd.img-$KVER" "$KVER" || true umount "$CHROOT/sys" "$CHROOT/proc" "$CHROOT/dev" # Populate /boot/efi/ for boot partition assembly mkdir -p "$CHROOT/boot/efi" cp "$CHROOT/boot/vmlinuz-$KVER" "$CHROOT/boot/efi/bzImage" if [ -f "$CHROOT/boot/initrd.img-$KVER" ]; then cp "$CHROOT/boot/initrd.img-$KVER" "$CHROOT/boot/efi/initrd.img" else INITRD=$(ls -1t "$CHROOT"/boot/initramfs-* "$CHROOT"/boot/initrd* 2>/dev/null | head -1) if [ -n "$INITRD" ]; then cp "$INITRD" "$CHROOT/boot/efi/initrd.img" else echo "WARNING: No initrd found for alpine after mkinitfs" fi fi echo ">> Alpine: kernel $KVER staged to boot/efi/" else echo "WARNING: No kernel modules found in .deb for alpine" fi fi # --- Create GPT disk image --- echo "=== Creating ${IMG_SIZE}MB disk image ===" TMPIMG="/build/ps5-${DISTRO}.img" dd if=/dev/zero of="$TMPIMG" bs=1M count=$IMG_SIZE conv=fsync status=progress parted -s "$TMPIMG" mklabel gpt parted -s "$TMPIMG" mkpart primary ext4 500MiB 100% parted -s "$TMPIMG" mkpart primary fat32 1MiB 500MiB parted -s "$TMPIMG" set 2 esp on # Ensure the free loop device node exists (udev doesn't run inside containers, # so when the kernel allocates a new loop number it may lack a /dev node) LOOP_PATH=$(losetup -f) if [ ! -e "$LOOP_PATH" ]; then LOOP_NUM=${LOOP_PATH#/dev/loop} mknod "$LOOP_PATH" b 7 "$LOOP_NUM" fi LOOPDEV=$(losetup -f --show "$TMPIMG") # Use kpartx to create partition device mappings (more reliable in containers) kpartx -av "$LOOPDEV" sleep 1 # kpartx creates /dev/mapper/loopXp1, /dev/mapper/loopXp2 LOOP_BASE=$(basename "$LOOPDEV") PART1="/dev/mapper/${LOOP_BASE}p1" PART2="/dev/mapper/${LOOP_BASE}p2" echo "=== Formatting partitions ===" mkfs.ext4 -L "$ROOT_LABEL" -m 1 "$PART1" mkfs.vfat -n "$EFI_LABEL" -F32 "$PART2" mkdir -p /tmp/usb_root /tmp/usb_efi mount "$PART1" /tmp/usb_root mount "$PART2" /tmp/usb_efi echo "=== Copying rootfs to image ===" cp -a "$CHROOT"/* /tmp/usb_root/ sync echo "=== Assembling boot partition ===" mv /tmp/usb_root/boot/efi/* /tmp/usb_efi/ 2>/dev/null || true sed "s|__DISTRO__|$ROOT_LABEL|" /repo/boot/cmdline.txt > /tmp/usb_efi/cmdline.txt cp /repo/boot/vram.txt /tmp/usb_efi/ cp /repo/boot/kexec.sh /tmp/usb_efi/ sync umount /tmp/usb_root /tmp/usb_efi rmdir /tmp/usb_root /tmp/usb_efi kpartx -dv "$LOOPDEV" losetup -d "$LOOPDEV" # Move finished image to output volume mv "$TMPIMG" "$IMG" sync echo "========================================" echo "Done! $IMG (${IMG_SIZE}MB)" echo "Flash: sudo dd if=$IMG of=/dev/sdX bs=4M status=progress" echo "========================================"