Skip to content

Instantly share code, notes, and snippets.

@bketelsen
Created December 8, 2025 20:46
Show Gist options
  • Select an option

  • Save bketelsen/9346ca3310b59605084a98015308a7be to your computer and use it in GitHub Desktop.

Select an option

Save bketelsen/9346ca3310b59605084a98015308a7be to your computer and use it in GitHub Desktop.
bootable img to iso
#!/bin/bash
set -e
# This script converts a raw image with 512 byte sectors to an iso with 2048 byte sectors. The conversion
# allows for booting of the resulting iso as a (virtual) CDROM.
if [ "$#" -ne 1 ]; then
echo "Usage: $0 <input img>"
exit 1
fi
if [ $EUID -ne 0 ]; then
echo "This script must be run as root"
exit 1
fi
SRC=$1
DST="${SRC//.img/.iso}"
cp "$SRC" "$DST"
truncate --size +1MiB "$DST"
sgdisk -Z "$DST"
SRCLOOPDEV=$(losetup --find --show --partscan "$SRC")
# Check actual space used on partition 3 (btrfs)
echo ""
echo "Checking space usage on partition 3 (btrfs)..."
MOUNT_TEMP=$(mktemp -d)
mount -o ro "${SRCLOOPDEV}p3" "$MOUNT_TEMP"
# Get btrfs filesystem usage
echo "Btrfs filesystem information:"
btrfs filesystem show "$MOUNT_TEMP" || true
btrfs filesystem usage "$MOUNT_TEMP" || true
# Get actual used space in bytes
USED_BYTES=$(btrfs filesystem usage -b "$MOUNT_TEMP" | grep "Used:" | head -1 | awk '{print $2}')
# Alternative if the above doesn't work
if [ -z "$USED_BYTES" ]; then
USED_BYTES=$(df -B1 "$MOUNT_TEMP" | tail -1 | awk '{print $3}')
fi
# Add 20% overhead for filesystem metadata and safety margin
USED_MB=$((USED_BYTES / 1024 / 1024))
OVERHEAD_MB=$((USED_MB / 5))
TOTAL_MB=$((USED_MB + OVERHEAD_MB + 512)) # Add extra 512MB for safety
echo ""
echo "Space analysis for partition 3:"
echo " Used space: ${USED_MB} MiB"
echo " Overhead (20%): ${OVERHEAD_MB} MiB"
echo " Total allocated: ${TOTAL_MB} MiB"
umount "$MOUNT_TEMP"
rmdir "$MOUNT_TEMP"
DSTLOOPDEV=$(losetup --sector-size 2048 --find --show "$DST")
PART1GUID=$(sgdisk -i 1 "$SRC" | grep "Partition unique GUID:" | sed -e "s/Partition unique GUID: //")
PART2GUID=$(sgdisk -i 2 "$SRC" | grep "Partition unique GUID:" | sed -e "s/Partition unique GUID: //")
PART3GUID=$(sgdisk -i 3 "$SRC" | grep "Partition unique GUID:" | sed -e "s/Partition unique GUID: //")
echo "PART1GUID: $PART1GUID"
echo "PART2GUID: $PART2GUID"
echo "PART3GUID: $PART3GUID"
PART1NAME=$(sgdisk -i 1 "$SRC" | grep "Partition name:" | sed -e "s/Partition name: '//" | sed -e "s/'//")
PART2NAME=$(sgdisk -i 2 "$SRC" | grep "Partition name:" | sed -e "s/Partition name: '//" | sed -e "s/'//")
PART3NAME=$(sgdisk -i 3 "$SRC" | grep "Partition name:" | sed -e "s/Partition name: '//" | sed -e "s/'//")
echo "PART1NAME: $PART1NAME"
echo "PART2NAME: $PART2NAME"
echo "PART3NAME: $PART3NAME"
sgdisk -n 1::+100MiB -u "1:$PART1GUID" -t 1:21686148-6449-6E6F-744E-656564454649 -c 1:"$PART1NAME" "$DSTLOOPDEV"
sgdisk -n 2::+512MiB -u "2:$PART2GUID" -t 2:c12a7328-f81f-11d2-ba4b-00a0c93ec93b -c 2:"$PART2NAME" "$DSTLOOPDEV"
# Use calculated size for partition 3 instead of filling all remaining space
sgdisk -n 3::+${TOTAL_MB}MiB -u "3:$PART3GUID" -t 3:4F68BCE3-E8CD-4DB1-96E7-FBCAF984B709 -c 3:"$PART3NAME" "$DSTLOOPDEV"
# sgdisk -n ::+16KiB -u "3:$PART3GUID" -t 3:E7BB33FB-06CF-4E81-8273-E543B413E2E2 -c "3:$PART3NAME" "$DSTLOOPDEV"
# sgdisk -n 4::+100MiB -u "4:$PART4GUID" -t 4:77FF5F63-E7B6-4633-ACF4-1565B864C0E6 -c "4:$PART4NAME" "$DSTLOOPDEV"
# sgdisk -n 5::+1024MiB -u "5:$PART5GUID" -t 5:8484680C-9521-48C6-9C11-B0720656F69E -c "5:$PART5NAME" "$DSTLOOPDEV"
partprobe "$DSTLOOPDEV"
echo ""
echo "Copying partitions..."
echo "Copying partition 1 (BIOS boot)..."
dd if="${SRCLOOPDEV}p1" of="${DSTLOOPDEV}p1" status=progress
echo "Copying partition 2 (EFI)..."
dd if="${SRCLOOPDEV}p2" of="${DSTLOOPDEV}p2" status=progress
echo "Copying partition 3 (Root btrfs) - using filesystem-aware copy..."
# Create btrfs filesystem on the smaller partition
mkfs.btrfs -f "${DSTLOOPDEV}p3"
# Mount both filesystems
SRC_MOUNT=$(mktemp -d)
DST_MOUNT=$(mktemp -d)
mount -o ro "${SRCLOOPDEV}p3" "$SRC_MOUNT"
mount "${DSTLOOPDEV}p3" "$DST_MOUNT"
# Copy files preserving all attributes and using compression
echo "Copying files from source to destination (this may take a while)..."
rsync -aAXHv --info=progress2 "$SRC_MOUNT/" "$DST_MOUNT/"
# Sync and unmount
sync
umount "$SRC_MOUNT"
umount "$DST_MOUNT"
rmdir "$SRC_MOUNT"
rmdir "$DST_MOUNT"
echo ""
echo "Final ISO size optimization..."
# Truncate the ISO to actual used size plus small buffer
ISO_END=$(sgdisk -E "$DSTLOOPDEV")
ISO_SECTOR_SIZE=2048
ISO_SIZE_BYTES=$((ISO_END * ISO_SECTOR_SIZE))
ISO_SIZE_MB=$((ISO_SIZE_BYTES / 1024 / 1024))
echo "ISO will be ${ISO_SIZE_MB} MiB (compared to original source image)"
losetup -d "$SRCLOOPDEV"
losetup -d "$DSTLOOPDEV"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment