Skip to content

Instantly share code, notes, and snippets.

@hongkongkiwi
Last active April 6, 2023 04:06
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save hongkongkiwi/eafa757081c28855efecce06c1073ce2 to your computer and use it in GitHub Desktop.
Save hongkongkiwi/eafa757081c28855efecce06c1073ce2 to your computer and use it in GitHub Desktop.
Loads a JFFS OpenWRT tar file to a USB drive from Mac. This is particularly useful for Ubiquiti EdgeRouter Lite devices with external USB drives.
#!/usr/bin/env bash
# BUILD INSTRUCTIONS:
# https://wiki.openwrt.org/toh/ubiquiti/edgerouter.lite?s[]=octeon
USAGE="Loads a OpenWRT tar file onto a USB\n$0 <openwrt.tar> [/dev/diskX]"
KERNEL_SIZE="148.9MB"
KERNEL_NAME="KERNEL"
KERNEL_FORMAT="MS-DOS FAT32"
DATA_NAME="DATA"
DATA_SIZE="0b"
DATA_FORMAT="UFSD_EXTFS4"
# trap ctrl-c and call ctrl_c()
trap ctrl_c INT
function ctrl_c() {
echo "Aborted!"
exit 255
}
# Sanity check
command -v "md5sum" >/dev/null 2>&1 || { echo >&2 "I require md5sum but it's not installed. Aborting."; exit 1; }
command -v "dd" >/dev/null 2>&1 || { echo >&2 "I require dd but it's not installed. Aborting."; exit 1; }
command -v "tar" >/dev/null 2>&1 || { echo >&2 "I require tar but it's not installed. Aborting."; exit 1; }
command -v "mktemp" >/dev/null 2>&1 || { echo >&2 "I require mktemp but it's not installed. Aborting."; exit 1; }
command -v "diskutil" >/dev/null 2>&1 || { echo >&2 "I require diskutil but it's not installed. Aborting."; exit 1; }
command -v "sed" >/dev/null 2>&1 || { echo >&2 "I require sed but it's not installed. Aborting."; exit 1; }
command -v "cut" >/dev/null 2>&1 || { echo >&2 "I require cut but it's not installed. Aborting."; exit 1; }
command -v "tr" >/dev/null 2>&1 || { echo >&2 "I require tr but it's not installed. Aborting."; exit 1; }
command -v "grep" >/dev/null 2>&1 || { echo >&2 "I require grep but it's not installed. Aborting."; exit 1; }
command -v "sudo" >/dev/null 2>&1 || { echo >&2 "I require sudo but it's not installed. Aborting."; exit 1; }
command -v "mdutil" >/dev/null 2>&1 || { echo >&2 "I require mdutil but it's not installed. Aborting."; exit 1; }
if [ "$(uname)" != "Darwin" ]; then
# Do something under Mac OS X platform
echo >&2 "Sorry, this script is designed to work on MacOSX only :-("
exit 20
fi
# Useful Functions
in_array() {
local hay needle=$1
shift
for hay; do
[[ $hay == $needle ]] && return 0
done
return 1
}
# /dev/disk3 (external, physical):
# #: TYPE NAME SIZE IDENTIFIER
# 0: FDisk_partition_scheme *4.0 GB disk3
# 1: DOS_FAT_32 KERNEL 148.9 MB disk3s1
# 2: Linux Untitled 1.8 GB disk3s2
# Get Command line arguments
TAR_FILE="$1"
USB_DISK="$2"
# If user doesn't supply a tar file then give them a selection
if [[ "$TAR_FILE" == "" ]]; then
OPTIONS=()
OPTION_FILES=()
IFS=$'\n'
FILES=(`find "$PWD" -name "*octeon*ext4*.tar"`)
for FILE in "${FILES[@]}"
do
:
# do whatever on $i
BASE_FILE=`basename "$FILE"`
OPTIONS+=( "$BASE_FILE" )
OPTION_FILES+=( "$FILE" )
done
if [ ${#OPTIONS[@]} -eq 0 ]; then
echo "ERROR: There are no tar files in current directory, please pass as program argument!"
exit 0
fi
OPTIONS+=("Quit")
PS3='Please select a tar file: '
select CHOICE in "${OPTIONS[@]}"; do
if [[ "$CHOICE" == "Quit" ]]; then
exit 0
elif [[ "$CHOICE" == "" ]]; then
echo "Invalid Selection"
elif [[ "$CHOICE" == "" ]]; then
exit 0
else
in_array "$CHOICE" "$OPTIONS"
TAR_FILE=${OPTION_FILES[${?}]}
break
fi
done
fi
base=`basename "$TAR_FILE"`
TAR_EXT="${base##*.}"
TAR_FILENAME="${base%.*}"
IMAGE_TYPE=`echo "$TAR_FILENAME" | sed 's/^.*octeon-//' | cut -f1 -d '-'`
# Check for valid file
if [[ ! -f "$TAR_FILE" ]]; then
echo >&2 "ERROR: Tar is a directory"
echo
echo -e "$USAGE"
exit 1
elif [[ "$TAR_EXT" != "tar" ]]; then
echo >&2 "ERROR: Invalid file specified (.tar is required)"
echo
echo -e "$USAGE"
exit 1
elif [[ `tar xOf "$TAR_FILE" &> /dev/null; echo $?` != 0 ]]; then
echo "ERROR: Invalid tar file (fails integrity check)"
echo
echo -e "$USAGE"
exit 1
fi
#check is script has been run as root /sudo
if [[ "$UID" != 0 ]] && [[ `sudo --non-interactive echo > /dev/null &> /dev/null; echo $?` != 0 ]]; then
echo "Looks like your not running the script using sudo, we will do it for you"
RESULT=`sudo -p "Please enter your user password so we can gain root privileges: " echo > /dev/null`
if [[ $? != 0 ]]; then
echo >&2 "ERROR: We were unable to get root privileges!"
echo >&2 " try running the script with sudo"
exit 10
fi
fi
# If USB drive is not passed, then list usb drives
if [[ "$USB_DISK" == "" ]]; then
DEVICES=`diskutil list | grep "/dev"`
OPTIONS=()
OPTION_DEVICES=()
while read -r DEVICE; do
DEVICE=`echo "$DEVICE" | cut -d' ' -f1`
DISKUTIL_INFO=`diskutil info "$DEVICE"`
PROTOCOL=`echo "$DISKUTIL_INFO" | grep 'Protocol:' | tr -d ' ' | cut -d':' -f2`
DEVICE_LOCATION=`echo "$DISKUTIL_INFO" | grep 'Device Location:' | tr -d ' ' | cut -d':' -f2`
REMOVABLE_MEDIA=`echo "$DISKUTIL_INFO" | grep 'Removable Media:' | tr -d ' ' | cut -d':' -f2`
if [ "$PROTOCOL" == "USB" -a "$DEVICE_LOCATION" == "External" -a "$REMOVABLE_MEDIA" == "Removable" ]; then
DEVICE_SIZE=`echo "$DISKUTIL_INFO" | grep 'Disk Size:' | tr -d ' ' | cut -d':' -f2 | cut -d'(' -f1`
DEVICE_NAME=`echo "$DISKUTIL_INFO" | grep "Device / Media Name:" | cut -d':' -f2 | sed -e 's/^[[:space:]]*//'`
OPTIONS+=("$DEVICE_NAME ($DEVICE_SIZE)")
OPTION_DEVICES+=("$DEVICE")
fi
done <<< "$DEVICES"
# Check if our devices list is empty
if [ ${#OPTIONS[@]} -eq 0 ]; then
echo "ERROR: Looks like there are no USB devices plugged in"
exit 0
fi
OPTIONS+=("Quit")
PS3='Please select USB device: '
select CHOICE in "${OPTIONS[@]}"; do
if [[ "$CHOICE" == "Quit" ]]; then
exit 0
elif [[ "$CHOICE" == "" ]]; then
echo "Invalid Selection"
elif [[ "$CHOICE" == "" ]]; then
exit 0
else
in_array "$CHOICE" "$OPTIONS"
USB_DISK=${OPTION_DEVICES[${?}]}
break
fi
done
elif [[ $(diskutil info "$USB_DISK" > /dev/null || echo "FAIL") == "FAIL" ]]; then
echo "ERROR: Invalid Device! \"$USB_DISK\""
echo
echo -e "$USAGE"
exit 1
fi
DISK_INFO=`diskutil info "$USB_DISK"`
IS_DISK=`if [[ $(echo "$DISK_INFO" | grep "Whole" | cut -f1 -d$'\n' | tr -d ' ' | cut -f2 -d ':') == "Yes" ]]; then echo "YES"; else echo "NO"; fi`
if [[ "$IS_DISK" == "NO" ]]; then
USB_DISK=`echo "$DISK_INFO" | grep "Part of Whole" | tr -d ' ' | cut -f2 -d ':'`
USB_DISK="/dev/${USB_DISK}"
DISK_INFO=`diskutil info "$USB_DISK"`
fi
IS_REMOVABLE=`if [[ $(echo "$DISK_INFO" | grep "Removable Media" | tr -d ' ' | cut -f2 -d ':') == "Removable" ]]; then echo "YES"; else echo "NO"; fi`
IS_USB=`if [[ $(echo "$DISK_INFO" | grep "Protocol" | tr -d ' ' | cut -f2 -d ':') == "USB" ]]; then echo "YES"; else echo "NO"; fi`
IS_VIRTUAL=`if [[ $(echo "$DISK_INFO" | grep "Virtual" | tr -d ' ' | cut -f2 -d ':') == "Yes" ]]; then echo "YES"; else echo "NO"; fi`
IS_EXTERNAL=`if [[ $(echo "$DISK_INFO" | grep "Device Location" | tr -d ' ' | cut -f2 -d ':') == "External" ]]; then echo "YES"; else echo "NO"; fi`
IS_READ_ONLY=`if [[ $(echo "$DISK_INFO" | grep "Read-Only Media" | tr -d ' ' | cut -f2 -d ':') == "Yes" ]]; then echo "YES"; else echo "NO"; fi`
DEVICE_NAME=`echo "$DISK_INFO" | grep "Device / Media Name" | cut -f2 -d ':' | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//'`
DISK_SIZE_BYTES=`echo "$DISK_INFO" | grep "Disk Size" | cut -f2 -d '(' | cut -f1 -d ' '`
DISK_SIZE_HUMAN=`echo "$DISK_INFO" | grep "Disk Size" | cut -f2 -d ':' | cut -f1 -d '(' | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//'`
if [ "$IS_REMOVABLE" == "NO" ] || [ "$IS_USB" == "NO" ] || [ "$IS_VIRTUAL" == "YES" ] || [ "$IS_EXTERNAL" == "NO" ]; then
echo >&2 "ERROR: Invalid USB Device"
echo
echo -e "$USAGE"
exit 1
fi
if [ "$IS_READ_ONLY" == "YES" ]; then
echo >&2 "ERROR: Device is read only!"
echo
echo -e "$USAGE"
exit 1
fi
echo "Looks like you want to use $USB_DISK - $DEVICE_NAME ($DISK_SIZE_HUMAN)"
echo "WARNING: This will format your disk and you will loose all data!"
read -r -p "Are you sure? [y/N] " response
case $response in
[yY][eE][sS]|[yY])
;;
*)
echo >&2 "Aborted!"
exit 1
;;
esac
echo "Starting Update Process"
NUMBER_OF_PARTITIONS=`diskutil list "$USB_DISK" | tail -n2 | wc -l`
NEEDS_PARTITION="NO"
if [[ "$NUMBER_OF_PARTITIONS" != 2 ]]; then
NEEDS_PARTITION="YES"
elif [[ `diskutil info "${USB_DISK}s1" | grep "Disk Size" | cut -f2 -d ':' | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' | tr -d ' ' | cut -f1 -d '('` != "$KERNEL_SIZE" ]]; then
NEEDS_PARTITION="YES"
else
PARTITIONS_INFO=`diskutil list "$USB_DISK" | tail -n2`
PARTITIONS_MOUNTS=()
PARTITIONS_DETAILS=()
COUNTER=1
while read -r PARTITION_INFO; do
PARTITION_MOUNT=`diskutil info "${USB_DISK}s${COUNTER}" | grep "Mount Point" | tr -d ' ' | cut -f2 -d ':'`
PARTITIONS_MOUNTS[$((COUNTER-1))]="$PARTITION_MOUNT"
PARTITIONS_DETAILS[$((COUNTER-1))]=`echo "$PARTITION_INFO" | cut -f2 -d ':' | tr -s ' ' | cut -f2 -d ' '`
COUNTER=$((COUNTER+1))
done <<< "$PARTITIONS_INFO"
if [[ "${PARTITIONS_DETAILS[0]}" != "DOS_FAT_32" ]]; then
NEEDS_PARTITION="YES"
fi
fi
if [[ "$NEEDS_PARTITION" == "YES" ]]; then
# Partition Disk
echo "# Partitioning USB Drive..."
FORMAT_RESULT=`diskutil partitionDisk "$USB_DISK" MBR "$KERNEL_FORMAT" "$KERNEL_NAME" "$KERNEL_SIZE" "$DATA_FORMAT" "$DATA_NAME" "$DATA_SIZE" || (echo >&2 "Failed to partition drive!"; exit 2)`
fi
echo "# Erasing Partition 1 (Kernel)..."
ERASE_RESULT=`diskutil eraseVolume "$KERNEL_FORMAT" "$KERNEL_NAME" "${USB_DISK}s1" || echo >&2 "Failed to erase kernel partition!"; exit 2;`
echo "# Erasing Partition 2 (Data)..."
ERASE_RESULT=`diskutil eraseVolume "$DATA_FORMAT" "%noformat%" "${USB_DISK}s2" || echo >&2 "Failed to erase data partition!"; exit 2;`
if [[ `tar -tf "$TAR_FILE" "sysupgrade-${IMAGE_TYPE}/root" >/dev/null 2>&1 || echo "FAIL"` == "FAIL" ]]; then
echo >&2 "${TAR_FILENAME}.${TAR_EXT} does not contain \"sysupgrade-${IMAGE_TYPE}/root\""
exit 5
elif [[ `tar -tf "$TAR_FILE" "sysupgrade-${IMAGE_TYPE}/kernel" >/dev/null 2>&1 || echo "FAIL"` == "FAIL" ]]; then
echo >&2 "${TAR_FILENAME}.${TAR_EXT} does not contain \"sysupgrade-${IMAGE_TYPE}/kernel\""
exit 5
fi
# Extract the new file
echo "# Extracting \"${TAR_FILENAME}.${TAR_EXT}\"..."
TMP_DIR=`mktemp -d`
UNZIPPED_DIR="${TMP_DIR}/sysupgrade-${IMAGE_TYPE}"
#tar vf "$TAR_FILE"
tar xf "$TAR_FILE" -C "$TMP_DIR"
echo "# Copying Kernel to partition..."
# Get mounted partition
KERNEL_MOUNT=`diskutil info "${USB_DISK}s1" | grep "Mount Point" | cut -f2 -d ':' | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//'`
DATA_MOUNT=`diskutil info "${USB_DISK}s2" | grep "Mount Point" | cut -f2 -d ':' | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//'`
if [[ ! -d "$KERNEL_MOUNT" ]]; then
echo >&2 "ERROR: Looks like we cannot find the correct mount point for kernel partition!"
exit 1
fi
TAR_LOCAL_DIR="$TMP_DIR/sysupgrade-${IMAGE_TYPE}"
KERNEL_LOCAL_FILE="$TAR_LOCAL_DIR/kernel"
DATA_LOCAL_FILE="$TAR_LOCAL_DIR/root"
# Check that we have the files we need
if [ ! -d "$TAR_LOCAL_DIR" ]; then
echo >&2 "ERROR: Looks like we cannot find the tmp dir we extracted to!"
exit 1
elif [ ! -f "$DATA_LOCAL_FILE" ]; then
echo >&2 "ERROR: Looks like we cannot find the \"root\" (data) file after extracting!"
exit 1
elif [ ! -f "$KERNEL_LOCAL_FILE" ]; then
echo >&2 "ERROR: Looks like we cannot find the \"kernel\" (kernel) file after extracting!"
exit 1
fi
COPY_RESULT=`cp -R "$KERNEL_LOCAL_FILE" "${KERNEL_MOUNT}/vmlinux.64" || (echo "Failed to copy Kernel!"; exit 0;)`
if [[ `md5sum "$KERNEL_LOCAL_FILE" | cut -f1 -d ' '` != `md5sum "${KERNEL_MOUNT}/vmlinux.64" | cut -f1 -d ' '` ]]; then
echo >&2 "ERROR: MD5 checksums do not match!!"
echo >&2 " Is your USB corrupted?"
exit 3
fi
KERNEL_MD5=`md5sum "$KERNEL_LOCAL_FILE" | cut -f1 -d ' ' | tee "${KERNEL_MOUNT}/vmlinux.64.md5"`
echo "# Wrote Kernel with MD5: $KERNEL_MD5"
# sudo mdutil -i off "$KERNEL_MOUNT"
# sudo mdutil -E "$KERNEL_MOUNT"
# cd "$KERNEL_MOUNT"
# rm -rf .{,_.}{fseventsd,Spotlight-V*,Trashes}
# mkdir .fseventsd
# touch .fseventsd/no_log .metadata_never_index .Trashes
# cd -
echo "# Writing Data Partition..."
UNMOUNT_RESULT=`diskutil unmount "${USB_DISK}s2" &> /dev/null`
# Format/Copy the data partition
DD_RESULT=`sudo dd if="$DATA_LOCAL_FILE" of="${USB_DISK}s2" bs=4096 &> /dev/null || (echo >&2 "Failed to write data partition!"; exit 1;)`
RENAME_RESULT=`diskutil renameVolume "${USB_DISK}s2" "$DATA_NAME"`
UNMOUNT_RESULT=`diskutil unmountDisk "$USB_DISK" &> /dev/null`
EJECT_RESULT=`diskutil eject "$USB_DISK" &> /dev/null`
echo "# Removing Volume Indexing..."
if [[ "$KERNEL_MOUNT" != "" ]] && [[ -d "$KERNEL_MOUNT" ]]; then
`sudo mdutil -i off "$KERNEL_MOUNT" &> /dev/null`
`sudo mdutil -E "$KERNEL_MOUNT" &> /dev/null`
# Removing existing crap
rm -R "${KERNEL_MOUNT}/."{,_.}{fseventsd,Spotlight-V*,Trashes,DS_Store} &> /dev/null
mkdir "${KERNEL_MOUNT}/.fseventsd"
touch "${KERNEL_MOUNT}/.fseventsd/no_log"
touch "${KERNEL_MOUNT}/.metadata_never_index"
touch "${KERNEL_MOUNT}/.Trashes"
fi
if [[ "$DATA_MOUNT" != "" ]] && [[ -d "$DATA_MOUNT" ]]; then
`sudo mdutil -i off "$DATA_MOUNT" &> /dev/null`
`sudo mdutil -E "$DATA_MOUNT" &> /dev/null`
# Removing existing crap
rm -R "${DATA_MOUNT}/."{,_.}{fseventsd,Spotlight-V*,Trashes,DS_Store} &> /dev/null
mkdir "${DATA_MOUNT}/.fseventsd"
touch "${DATA_MOUNT}/.fseventsd/no_log"
touch "${DATA_MOUNT}/.metadata_never_index"
touch "${DATA_MOUNT}/.Trashes"
fi
echo "# Cleaning Up..."
if [ "$TMP_DIR" != "" ] && [ "$TMP_DIR" != "/" ]; then
rm -R "$TMP_DIR"
fi
echo "Complete! :-)"
echo -e "\nPut your USB drive into your router and boot"
exit 0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment