diff --git a/smart_led/upload.sh b/smart_led/upload.sh new file mode 100755 index 0000000..d0848bf --- /dev/null +++ b/smart_led/upload.sh @@ -0,0 +1,154 @@ +#!/bin/sh + +# +# Upload new firmware to a target running nerves_firmware_ssh +# +# Usage: +# upload.sh [destination IP] [Path to .fw file] +# +# If unspecifed, the destination is nerves.local and the .fw file is naively +# guessed +# +# You may want to add the following to your `~/.ssh/config` to avoid recording +# the IP addresses of the target: +# +# Host nerves.local +# UserKnownHostsFile /dev/null +# StrictHostKeyChecking no +# +# The firmware update protocol is: +# +# 1. Connect to the nerves_firmware_ssh service running on port 8989 +# 2. Send "fwup:$FILESIZE,reboot\n" where `$FILESIZE` is the size of the file +# being uploaded +# 3. Send the firmware file +# 4. The response from the device is a progress bar from fwup that can either +# be ignored or shown to the user. +# 5. The ssh connection is closed with an exit code to indicate success or +# failure +# +# Feel free to copy this script wherever is convenient. The template is at +# https://github.com/nerves-project/nerves_firmware_ssh/blob/master/priv/templates/script.upload.eex +# + +set -e + +DESTINATION=$1 +FILENAME="$2" + +help() { + echo + echo "upload.sh [destination IP] [Path to .fw file]" + echo + echo "Default destination IP is 'nerves.local'" + echo "Default firmware bundle is the first .fw file in '_build/\${MIX_TARGET}_\${MIX_ENV}/nerves/images'" + echo + echo "MIX_TARGET=$MIX_TARGET" + echo "MIX_ENV=$MIX_ENV" + exit 1 +} + +[ -n "$DESTINATION" ] || DESTINATION=nerves.local +[ -n "$MIX_TARGET" ] || MIX_TARGET=rpi0 +[ -n "$MIX_ENV" ] || MIX_ENV=dev +if [ -z "$FILENAME" ]; then + FIRMWARE_PATH="./_build/${MIX_TARGET}_${MIX_ENV}/nerves/images" + if [ ! -d "$FIRMWARE_PATH" ]; then + # Try the Nerves 1.4 path if the user hasn't upgraded their mix.exs + FIRMWARE_PATH="./_build/${MIX_TARGET}/${MIX_TARGET}_${MIX_ENV}/nerves/images" + if [ ! -d "$FIRMWARE_PATH" ]; then + # Try the pre-Nerves 1.4 path + FIRMWARE_PATH="./_build/${MIX_TARGET}/${MIX_ENV}/nerves/images" + if [ ! -d "$FIRMWARE_PATH" ]; then + echo "Can't find the build products." + echo + echo "Nerves environment" + echo "MIX_TARGET: ${MIX_TARGET}" + echo "MIX_ENV: ${MIX_ENV}" + echo + echo "Make sure your Nerves environment is correct." + echo + echo "If the Nerves environment is correct make sure you have built the firmware" + echo "using 'mix firmware'." + echo + echo "If you are uploading a .fw file from a custom path you can specify the" + echo "path like so:" + echo + echo " $0 " + echo + exit 1 + fi + fi + fi + + FILENAME=$(ls "$FIRMWARE_PATH/"*.fw 2> /dev/null | head -n 1) +fi + +[ -n "$FILENAME" ] || (echo "Error: error determining firmware bundle."; help) +[ -f "$FILENAME" ] || (echo "Error: can't find '$FILENAME'"; help) + +# Check the flavor of stat for sending the filesize +if stat --version 2>/dev/null | grep GNU >/dev/null; then + # The QNU way + FILESIZE=$(stat -c%s "$FILENAME") +else + # Else default to the BSD way + FILESIZE=$(stat -f %z "$FILENAME") +fi + +FIRMWARE_METADATA=$(fwup -m -i "$FILENAME" || echo "meta-product=Error reading metadata!") +FIRMWARE_PRODUCT=$(echo "$FIRMWARE_METADATA" | grep -E "^meta-product=" -m 1 2>/dev/null | cut -d '=' -f 2- | tr -d '"') +FIRMWARE_VERSION=$(echo "$FIRMWARE_METADATA" | grep -E "^meta-version=" -m 1 2>/dev/null | cut -d '=' -f 2- | tr -d '"') +FIRMWARE_PLATFORM=$(echo "$FIRMWARE_METADATA" | grep -E "^meta-platform=" -m 1 2>/dev/null | cut -d '=' -f 2- | tr -d '"') +FIRMWARE_UUID=$(echo "$FIRMWARE_METADATA" | grep -E "^meta-uuid=" -m 1 2>/dev/null | cut -d '=' -f 2- | tr -d '"') + +echo "Path: $FILENAME" +echo "Product: $FIRMWARE_PRODUCT $FIRMWARE_VERSION" +echo "UUID: $FIRMWARE_UUID" +echo "Platform: $FIRMWARE_PLATFORM" +echo +echo "Uploading to $DESTINATION..." + +# Don't fall back to asking for passwords, since that won't work +# and it's easy to misread the message thinking that it's asking +# for the private key password +SSH_OPTIONS="-o PreferredAuthentications=publickey" + +if [ "$(uname -s)" = "Darwin" ]; then + DESTINATION_IP=$(arp -n $DESTINATION | sed 's/.* (\([0-9.]*\).*/\1/' || exit 0) + if [ -z "$DESTINATION_IP" ]; then + echo "Can't resolve $DESTINATION" + exit 1 + fi + TEST_DESTINATION_IP=$(printf "$DESTINATION_IP" | head -n 1) + if [ "$DESTINATION_IP" != "$TEST_DESTINATION_IP" ]; then + echo "Multiple destination IP addresses for $DESTINATION found:" + echo "$DESTINATION_IP" + echo "Guessing the first one..." + DESTINATION_IP=$TEST_DESTINATION_IP + fi + + IS_DEST_LL=$(echo $DESTINATION_IP | grep '^169\.254\.' || exit 0) + if [ -n "$IS_DEST_LL" ]; then + LINK_LOCAL_IP=$(ifconfig | grep 169.254 | sed 's/.*inet \([0-9.]*\) .*/\1/') + if [ -z "$LINK_LOCAL_IP" ]; then + echo "Can't find an interface with a link local address?" + exit 1 + fi + TEST_LINK_LOCAL_IP=$(printf "$LINK_LOCAL_IP" | tail -n 1) + if [ "$LINK_LOCAL_IP" != "$TEST_LINK_LOCAL_IP" ]; then + echo "Multiple interfaces with link local addresses:" + echo "$LINK_LOCAL_IP" + echo "Guessing the last one, but YMMV..." + LINK_LOCAL_IP=$TEST_LINK_LOCAL_IP + fi + + # If a link local address, then force ssh to bind to the link local IP + # when connecting. This fixes an issue where the ssh connection is bound + # to another Ethernet interface. The TCP SYN packet that goes out has no + # chance of working when this happens. + SSH_OPTIONS="$SSH_OPTIONS -b $LINK_LOCAL_IP" + fi +fi + +printf "fwup:$FILESIZE,reboot\n" | cat - $FILENAME | ssh -s -p 8989 $SSH_OPTIONS $DESTINATION nerves_firmware_ssh