Last active
April 6, 2023 04:06
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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