Skip to content

Instantly share code, notes, and snippets.

@jeff47
Created September 28, 2025 00:41
Show Gist options
  • Select an option

  • Save jeff47/e35e5a2d2a6258e7a8a1e75b962c5b30 to your computer and use it in GitHub Desktop.

Select an option

Save jeff47/e35e5a2d2a6258e7a8a1e75b962c5b30 to your computer and use it in GitHub Desktop.
Resizing a disk image (shrink)

Purpose: Create a flashable image of a drive and resize it so unused space is minimized. Created image can be flashed as a backup that contains all config files.

Application: Backups of router and pihole.


Before starting, make sure the disk to be imaged is unmounted.

  1. Image the source drive: sudo dd if=/dev/sdb of=image.img bs=4M status=progress If space is an issue, dd can be piped straight to gzip, but you'll need to unzip before you can manipulate the image. sudo dd if=/dev/sdb bs=4M status=progress | gzip -9 > image.img.gz

  2. MAKE A BACKUP OF THE IMAGE. (e.g., cp image.img working.img)

  3. Save the working image filename into an environmental variable to make things easier. `export LOOPIMAGE="working.img"

  4. Mount the image using the loop device and set environmental variables so subsequent commands work when cut/paste, also show the user the partitions for a sanity check. This and subsequent commands assume that the second partition is to be resized. If this is not true, then $LOOPPART needs to be manually set.

LOOPDEV=$(sudo losetup -fP --show ${LOOPIMAGE})
lsblk "$LOOPDEV" && LOOPPART=$(lsblk -brno NAME,TYPE,START "$LOOPDEV" | awk '$2=="part"{p=$1;s=$3} END{print "/dev/"p}') && printf "\nLoop device: %s\nPartition to resize: %s\n" "$LOOPDEV" "$LOOPPART"
  1. Check and repair errors: sudo e2fsck -f ${LOOPPART}

  2. Find minimum file system size. This will output the size in blocks and MiB.

min=$(sudo resize2fs -P ${LOOPPART} 2>/dev/null | awk '{print $7}'); \
bsize=$(sudo dumpe2fs -h ${LOOPPART} 2>/dev/null | awk '/Block size:/ {print $3}'); \
echo "Block size: $bsize"; \
echo "Minimum blocks: $min"; \
echo "Min FS size:   ${min} blocks ($((min*bsize/1024/1024)) MiB)"; \
echo "1.5x FS size:  $((min*3/2)) blocks ($((min*3/2*bsize/1024/1024)) MiB)"; \
echo "2x FS size:    $((min*2)) blocks ($((min*2*bsize/1024/1024)) MiB)"; \
echo "sudo resize2fs ${LOOPPART} $((min*3/2))"
  1. Shrink the file system. To be safe, pick a size 1.5-2X the minimum. resize2fs expects blocks. Providing some extra space (1.5X) will give some leeway when restoring.

  2. Calculate numbers for the new partition. This sets the new end of the partition to [start of partition 2 + new filesystem size + 50–100 MiB slack]

partnum=$(echo "$LOOPPART" | grep -o '[0-9]\+$')
fsinfo=$(sudo dumpe2fs -h ${LOOPPART} 2>/dev/null | awk '/Block count:/ {count=$3} /Block size:/ {size=$3} END {print count*size}')
pstart=$(sudo parted -sm ${LOOPDEV} unit B print | awk -F: -v pn="$partnum" '$1==pn {sub(/B$/,"",$2); print $2}')
margin=$((fsinfo + 1024*1024))
end=$((pstart + margin))
printf "\n\nFS size:   %s bytes \nPart start:  %s bytes\nEnd+1MiB:  %s bytes\nRun this:\n sudo parted %s unit B resizepart %s %sB\n" "$fsinfo" "$pstart" "$end" "$LOOPDEV" "$partnum" "$end"

Resize the partition using the recommended command.

  1. Verify file system fits. sudo e2fsck -f ${LOOPPART}

  2. Find the new partition end. This rounds up to the next MiB boundary to ensure full compatibility.

img=$(losetup -a | awk -v dev="$LOOPDEV" -F'[()]' '$1 ~ dev {print $2}')
sudo parted -sm ${LOOPDEV} unit B print \
| awk -v img="$img" -v dev="$LOOPDEV" -F: '$1 ~ /^[0-9]+$/ {end=$3} END {
    sub(/B$/,"",end)
    mib=1024*1024
    rounded=int((end + mib - 1) / mib) * mib
    safe=rounded + mib
    printf "End: %s bytes\n", end
    printf "Rounded: %d bytes (next MiB)\n", rounded
    printf "Safe +1M: %d bytes\n", safe
    print "\n👉 Before truncating, detach the loop device:"
    printf "  sudo losetup -d %s\n", dev
    print "\nThen run:"
    printf "  sudo truncate -s %d \"%s\"\n", safe, img
}'

Dismount the loop device, then truncate using the command suggested.

  1. Check file system again.
sudo losetup -fP ${LOOPIMAGE}
sudo e2fsck -f ${LOOPPART}
sudo losetup -d ${LOOPDEV}
  1. Check image is readable
dd if=${LOOPIMAGE} of=/dev/null bs=1M status=progress
  1. Zip it for additional space savings. gzip -9 ${LOOPIMAGE}

  2. Remove the original image.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment