#!/bin/bash # Pull Raspberry Pi image from SD card (after setup) # Resizes rootfs to 16GB for consistent image size, then pulls # # Usage: ./pull-image.sh # Example: ./pull-image.sh /dev/sdb stegasoo-rpi-4.2.1.img.zst set -e RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BOLD='\033[1m' NC='\033[0m' if [ $# -ne 2 ]; then echo "Usage: $0 " echo "Example: $0 /dev/sdb stegasoo-rpi-4.2.1.img.zst" exit 1 fi DEVICE="$1" OUTPUT="$2" # Check for root if [ "$EUID" -ne 0 ]; then echo -e "${RED}Error: Must run as root (sudo)${NC}" exit 1 fi if [ ! -b "$DEVICE" ]; then echo -e "${RED}Error: Device not found: $DEVICE${NC}" exit 1 fi echo -e "${BOLD}Device info:${NC}" lsblk "$DEVICE" echo # Find partitions if [ -b "${DEVICE}1" ]; then BOOT_PART="${DEVICE}1" ROOT_PART="${DEVICE}2" elif [ -b "${DEVICE}p1" ]; then BOOT_PART="${DEVICE}p1" ROOT_PART="${DEVICE}p2" else echo -e "${RED}Error: Could not find partitions${NC}" exit 1 fi # Unmount any mounted partitions echo -e "${YELLOW}Unmounting partitions...${NC}" umount "$BOOT_PART" 2>/dev/null || true umount "$ROOT_PART" 2>/dev/null || true # ============================================================================ # Resize rootfs to 16GB # ============================================================================ echo echo -e "${BOLD}Checking partition size...${NC}" # Get current partition size in bytes CURRENT_SIZE=$(blockdev --getsize64 "$ROOT_PART") TARGET_BYTES=$((16 * 1024 * 1024 * 1024)) # 16GB in bytes CURRENT_GB=$(echo "scale=2; $CURRENT_SIZE / 1073741824" | bc) echo " Current rootfs size: ${CURRENT_GB}GB" if [ "$CURRENT_SIZE" -gt "$TARGET_BYTES" ]; then echo -e "${YELLOW}Resizing rootfs to 16GB...${NC}" # Get boot partition end in sectors BOOT_END=$(parted -s "$DEVICE" unit s print | grep "^ 1" | awk '{print $3}' | tr -d 's') # Calculate 16GB in sectors (512 byte sectors) ROOT_SIZE_SECTORS=33554432 ROOT_END=$((BOOT_END + ROOT_SIZE_SECTORS)) # SHRINKING: filesystem first, then partition echo " Checking filesystem..." e2fsck -f -y "$ROOT_PART" 2>/dev/null || true # Shrink filesystem to 15.5GB (leave room for partition overhead) echo " Shrinking filesystem to 15500M..." resize2fs "$ROOT_PART" 15500M # Delete and recreate partition 2 with 16GB size echo " Shrinking partition to 16GB..." parted -s "$DEVICE" rm 2 parted -s "$DEVICE" mkpart primary ext4 $((BOOT_END + 1))s ${ROOT_END}s # Refresh partition table partprobe "$DEVICE" sleep 2 # Expand filesystem to fill the partition exactly echo " Expanding filesystem to fill partition..." e2fsck -f -y "$ROOT_PART" 2>/dev/null || true resize2fs "$ROOT_PART" echo -e "${GREEN} Rootfs resized to 16GB${NC}" elif [ "$CURRENT_SIZE" -lt "$TARGET_BYTES" ]; then echo -e "${YELLOW} Rootfs is smaller than 16GB - expanding...${NC}" # Get boot partition end in sectors BOOT_END=$(parted -s "$DEVICE" unit s print | grep "^ 1" | awk '{print $3}' | tr -d 's') ROOT_SIZE_SECTORS=33554432 ROOT_END=$((BOOT_END + ROOT_SIZE_SECTORS)) # EXPANDING: partition first, then filesystem parted -s "$DEVICE" rm 2 parted -s "$DEVICE" mkpart primary ext4 $((BOOT_END + 1))s ${ROOT_END}s partprobe "$DEVICE" sleep 2 e2fsck -f -y "$ROOT_PART" 2>/dev/null || true resize2fs "$ROOT_PART" echo -e "${GREEN} Rootfs expanded to 16GB${NC}" else echo -e "${GREEN} Rootfs already ~16GB${NC}" fi # ============================================================================ # Pull image # ============================================================================ echo echo -e "${BOLD}Partition table:${NC}" parted -s "$DEVICE" unit s print echo # Get the end of the last partition (partition 2 = rootfs) END_SECTOR=$(parted -s "$DEVICE" unit s print | grep "^ 2" | awk '{print $3}' | tr -d 's') if [ -z "$END_SECTOR" ]; then echo -e "${RED}Error: Could not determine partition 2 end sector${NC}" exit 1 fi # Add a small buffer (1MB = 2048 sectors) for safety TOTAL_SECTORS=$((END_SECTOR + 2048)) TOTAL_BYTES=$((TOTAL_SECTORS * 512)) TOTAL_GB=$(echo "scale=2; $TOTAL_BYTES / 1073741824" | bc) echo -e "Image size: ${YELLOW}~${TOTAL_GB}GB${NC} (${TOTAL_SECTORS} sectors)" echo -e "Output: ${YELLOW}$OUTPUT${NC}" echo read -p "Proceed with image pull? [Y/n] " confirm if [[ "$confirm" =~ ^[Nn]$ ]]; then echo "Aborted." exit 1 fi echo echo -e "${GREEN}Pulling image...${NC}" echo # Use pv if available for progress, otherwise fallback to dd status if command -v pv &>/dev/null; then dd if="$DEVICE" bs=512 count=$TOTAL_SECTORS 2>/dev/null | pv -s $TOTAL_BYTES | zstd -T0 -19 --ultra >"$OUTPUT" else dd if="$DEVICE" bs=512 count=$TOTAL_SECTORS status=progress | zstd -T0 -19 --ultra >"$OUTPUT" fi echo echo -e "${GREEN}Done!${NC} Image saved to: $OUTPUT" ls -lh "$OUTPUT" # ============================================================================ # Optional: Zip-wrap for GitHub releases # ============================================================================ echo read -p "Create .zst.zip wrapper for GitHub? [y/N] " zip_confirm if [[ "$zip_confirm" =~ ^[Yy]$ ]]; then ZIP_OUTPUT="${OUTPUT}.zip" echo -e "${YELLOW}Creating zip wrapper (store mode, no compression)...${NC}" zip -0 "$ZIP_OUTPUT" "$OUTPUT" echo -e "${GREEN}Done!${NC} Upload this to GitHub Releases:" ls -lh "$ZIP_OUTPUT" echo echo "Users can flash with:" echo " sudo ./rpi/flash-image.sh $ZIP_OUTPUT" else echo echo "To verify:" echo " zstdcat $OUTPUT | fdisk -l /dev/stdin" fi