#!/bin/bash -e set -o pipefail # Writes a raw image (possibly zipped) # to a device (e.g., USB drive or SD card) # using dd, with status monitoring via pv ARGS=$(getopt -o fh -l "force,sparse,debug,removable,fixed,help" -n "$0" -- "$@") eval set -- "$ARGS" usage() { cat <&2 exit 1 fi REMOVABLE=1 shift ;; --fixed) if [ "$REMOVABLE" = "1" ]; then echo "--removable and --fixed cannot both be used" >&2 exit 1 fi REMOVABLE=0 shift ;; -h|--help) usage exit 0 ;; --) shift break ;; esac done declare -A dependencies dependencies=( [dd]='coreutils' [xzcat]='xz-utils' [zcat]='gzip' ) for command in "${!dependencies[@]}"; do if ! which "$command" >/dev/null 2>&1; then echo "$command is not installed... aborting!" >&2 echo "Try 'sudo apt-get install ${dependencies[$command]}'" >&2 exit 1 fi done if [ $# -lt 2 ] || [ $# -gt 3 ] ; then if [ $# -eq 0 ]; then echo "Error: missing IMAGE and DEVICE arguments" >&2 elif [ $# -eq 1 ]; then echo "Error: missing DEVICE argument" >&2 else echo "Error: extra arguments after IMAGE DEVICE BLOCK_SIZE" >&2 fi echo >&2 usage >&2 exit 1 fi USERID=$(id -u) IMAGE="$1" DEVICE="$2" BLOCK_SIZE=1M if [ $# -ge 3 ] ; then BLOCK_SIZE="$3" fi DD_IN_OPTS+=(bs="$BLOCK_SIZE") DD_OUT_OPTS+=(bs="$BLOCK_SIZE") if [ "$DEVICE" != "-" ] && [ "$USERID" != "0" ]; then echo "Program requires superuser privileges" >&2 exit 1 fi if [ ! -f "$IMAGE" ]; then echo "$IMAGE not found" >&2 exit 1 fi # fall back if pv isn't available if ! which pv >/dev/null 2>&1; then pv() { echo "Proceeding with no pv, there will be no progress message!" >&2 echo "Try 'sudo apt-get install pv'" >&2 echo "Writing..." >&2 cat echo "Done!" >&2 } fi cat_image() { MIME_TYPE="$(file --brief --mime-type "$IMAGE")" if [[ "$MIME_TYPE" =~ xz$ ]]; then # Image is xzipped, xz has its own rate meter xzcat -v "$IMAGE" elif [[ "$MIME_TYPE" =~ gzip$ ]]; then # Image is gzipped # The following would calculate the original size # of the unzipped file if less than 4G: # IMAGE_SIZE=$(zcat -l "$IMAGE" | awk 'NR==2 { print $2 }') # But this doesn't help for large images, # so we just show relative progress unless .size is present if [ -f "$IMAGE.size" ]; then IMAGE_SIZE=$(cat $IMAGE.size) PV_ARGS="-s $IMAGE_SIZE" fi zcat "$IMAGE" | pv $PV_ARGS else # Image is uncompressed # We can show progress as percentage of total image size IMAGE_SIZE=$(ls -l "$IMAGE" | awk '{ print $5 }') dd if="$IMAGE" "${DD_IN_OPTS[@]}" | pv -s ${IMAGE_SIZE} # ISO images do not need to be mangled if [[ "$MIME_TYPE" =~ x-iso9660-image$ ]]; then REMOVABLE=0 fi fi } if [ "$DEVICE" == "-" ]; then if [ "$REMOVABLE" = "1" ]; then echo "--removable not supported when writing to stdout" >&2 exit 1 fi cat_image else if [ ! -e "$DEVICE" ]; then echo "$DEVICE does not exist... aborting!" >&2 exit 1 fi if [ ! -b "$DEVICE" ]; then echo "$DEVICE is not a block device... aborting!" >&2 exit 1 fi if grep -qs "$DEVICE" /proc/mounts; then # Protect against overwriting the device currently in use echo "$DEVICE is currently in use -- please unmount and try again" >&2 exit 1 fi if [ ! "$REMOVABLE" ]; then if ! REMOVABLE=$(lsblk --raw --nodeps --noheading --output HOTPLUG "$DEVICE"); then # This can happen if the disk has been "ejected" rather than # unmounting. Writing to it will fail in this case. echo "Unable to detect whether $DEVICE is removable;" \ "writing to it will probably fail." >&2 REMOVABLE=1 elif [ "$REMOVABLE" = 1 ]; then echo "Guessed that $DEVICE is a removable disk;" \ "pass --fixed if this is wrong." >&2 else echo "Guessed that $DEVICE is a fixed disk;" \ "pass --removable if this is wrong." >&2 fi fi if [ ! "$FORCE" ]; then read -p "Are you sure you want to overwrite all data on $DEVICE? [y/N] " response="${REPLY,,}" # to lower if [[ ! "$response" =~ ^(yes|y)$ ]]; then exit 1 fi fi # Try to format / discard all the sectors on the device blkdiscard "$DEVICE" &>/dev/null || true cat_image | dd of="$DEVICE" "${DD_OUT_OPTS[@]}" # Probe new partitions partprobe "$DEVICE" udevadm settle # If removable device, delete fallback from the ESP if [ "$REMOVABLE" = "1" ]; then # Assume ESP is the first partition case "$DEVICE" in /dev/mmcblk?) ESP=${DEVICE}p1 ;; /dev/sd?) ESP=${DEVICE}1 ;; esac # Try to mount ESP udisksctl mount -b "$ESP" --no-user-interaction || exit 1 # Grab the mountpoint ESP_MP=$(grep "$ESP" /proc/mounts | cut -f2 -d' ') if test -n "$ESP_MP" -a -d "$ESP_MP" then ESP_BOOT="${ESP_MP}/EFI/BOOT" ESP_ENDLESS="${ESP_MP}/EFI/endless" FALLBACK_OLD="${ESP_BOOT}/fallback.efi" FALLBACK_NEW="${ESP_BOOT}/fbx64.efi" MOKMANAGER_OLD="${ESP_ENDLESS}/MokManager.efi" MOKMANAGER_NEW="${ESP_ENDLESS}/mmx64.efi" GRUB="${ESP_ENDLESS}/grubx64.efi" echo "Checking ESP layout" >&2 if [ -f "$FALLBACK_OLD" ]; then echo "Found $FALLBACK_OLD" >&2 FALLBACK=$FALLBACK_OLD MOKMANAGER=$MOKMANAGER_OLD elif [ -f "$FALLBACK_NEW" ]; then echo "Found $FALLBACK_NEW" >&2 FALLBACK=$FALLBACK_NEW MOKMANAGER=$MOKMANAGER_NEW fi if [ -z "$FALLBACK" ]; then echo "Neither $FALLBACK_OLD nor $FALLBACK_NEW were found" >&2 else rm -v "$FALLBACK" mv -v "$MOKMANAGER" "$ESP_BOOT" mv -v "$GRUB" "$ESP_BOOT" rm -frv "$ESP_ENDLESS" fi fi # Unmount ESP udisksctl unmount -b "$ESP" --no-user-interaction fi fi