Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #!/bin/bash
- #
- # perform a system update, given a new rootfs.tar[.gz] file
- #
- if [ -s /etc/sysupdate.conf ]; then
- . /etc/sysupdate.conf
- else
- server=api.aether.net
- PKG_CACHE=/var/music/data/update
- PKG_MOUNT=/var/music/data/update/mnt
- PART_MOUNT=/mnt
- MCA_CERT_DIR=/etc/ssl/certs/aether
- CA_BUNDLE_AETHER_SERVERS=/etc/ssl/certs/aether-servers-ca-bundle.crt
- CA_BUNDLE_DOWNLOAD=/etc/ssl/certs/ca-bundle.crt
- cert_temp=
- LOCK_FILE=/var/run/sysupdate.pid
- INSIDE_FILE=rootfs.tar.gz
- status_file=
- DATABASE=/var/tuned/sysupdated.db
- fi
- config() {
- mp-config --config=/etc/tuned/tuned.conf --override get ${@+"$@"}
- }
- # Exits the script with a defined return status and, optionally,
- # writes a string of text into the status file if both the text
- # and status file are specified.
- function exit_status()
- {
- if [ "${status_file}" != "" ]; then
- echo "$2" > ${status_file}
- fi
- if [ -f $DATABASE ]; then
- echo "INSERT INTO update_state(result) VALUES ('$2');" | sqlite3 ${DATABASE}
- fi
- if grep -q ${PKG_MOUNT} /proc/mounts
- then
- fusermount -u ${PKG_MOUNT}
- fi
- echo "Terminating sysupdate with status $1 due to $2"
- exit $1
- }
- function clean()
- {
- echo "Cleaning up ..."
- if grep -q ${PKG_MOUNT} /proc/mounts
- then
- fusermount -u ${PKG_MOUNT}
- fi
- while [ -d ${PKG_MOUNT} ]; do
- echo "Waiting for metfs to finish unmounting"
- sleep 1
- rmdir ${PKG_MOUNT}
- done
- rm -f ${cert_temp}
- rm -f ${pubkey_temp}
- if [ -n "${update_complete}" -o -n "${invalid_image}" ]; then
- if [ -n "${download_url}" ] && [ -f "${update_image}" ]; then
- echo "Removing downloaded update file. ${update_image}"
- rm -f "${update_image}"
- fi
- fi
- rm -f ${LOCK_FILE}
- if [ -n "${need_reboot}" ]; then
- echo "Reboot needed to update to $new_version"
- sync
- sleep 15
- sync
- exit_status 0 "Applied $new_version"
- fi
- }
- function download()
- {
- current_version=${1}
- alt_version=${2}
- factory_version=${3}
- # Check that these configuration files are present, as well
- if [ ! -f /etc/tuned/tuned.conf ]; then
- echo "Error: Configuration file /etc/tuned/tuned.conf does not exist"
- exit_status 1 "Configuration"
- fi
- if [ ! -f /var/etc/.certs/device_id ]; then
- echo "Error: Device ID does not exist"
- exit_status 1 "Configuration"
- fi
- if [ -f /var/root/.sysupdate_exempt -a "${force}" -ne "1" ]; then
- echo "Not attempting to download: device is exempt from updates"
- exit_status 1 "Exempt"
- fi
- if [ "x${current_version}" == "x" -o "x${alt_version}" == "x" ]; then
- echo "Error: current [${current_version}] and alt [${alt_version}] versions need to be supplied, were not"
- exit_status 1 "Configuration"
- fi
- # Do a quick sanity check of the date to ensure the clock is set to a somewhat reasonable value (the MCU epoch time is 2000)
- # This is necessary for certificate validation
- year=$(date -I | cut -c1-4)
- if [ ${year} -lt 2014 ]; then
- echo "Please check the system clock, it seems to be set incorrectly. We think the year is ${year}."
- exit_status 1 "Date"
- fi
- cert_file=$(config main.cert-file)
- cert_pass=$(config main.cert-password)
- cert_temp=$(mktemp -q -t cert.XXXXXX)
- device_id=$(cat /var/etc/.certs/device_id)
- # Prior to invoking openssl, check that the certificate file is present
- if [ ! -f ${cert_file} ]; then
- echo "Error: Certificate file ${cert_file} does not exist!"
- exit_status 1 "Configuration"
- fi
- openssl pkcs12 -in ${cert_file} -passin pass:${cert_pass} -nomacver -nodes -out "${cert_temp}"
- url="https://${update_server}/services/2012.257/devices/${device_id}/firmware?current_version=${current_version}&force=${force}&alt_version=${alt_version}&factory_version=${factory_version}"
- echo "Querying server to see if update is available: ${url}"
- # A large connect timeout is needed to allow for ipv6/ipv4/multiple DNS servers to timeout and work. TUNE-3796
- download_url=$(curl -s -S -f --connect-timeout 25 --max-time 300 --cert "${cert_temp}" --cacert "${CA_BUNDLE_AETHER_SERVERS}" "${url}")
- res=$?
- if [ $res -ne 0 ]; then
- echo "Error: ${download_url} failed with ${res}"
- exit_status $res "Network"
- fi
- if [ -z "$download_url" ]; then
- echo "No update available"
- exit_status 0 "No Update"
- fi
- echo "Update available at: ${download_url}"
- file_name=$(basename "${download_url}")
- mkdir -p "${PKG_CACHE}"
- #
- # ensure that there is sufficient space in $PKG_CACHE
- #
- free_blocks=$(df -P "${PKG_CACHE}" | sed -n 2p | awk '{print $4}')
- if [ "${free_blocks}" -lt 100000 ]; then
- echo "Insufficient space in $PKG_CACHE, using /tmp instead"
- PKG_CACHE=/tmp
- fi
- update_image="${PKG_CACHE}/${file_name}"
- pushd "${PKG_CACHE}"
- # If the filename exists, then it has been fully downloaded.
- if [ ! -f ${file_name} ]; then
- # A large connect timeout is needed to allow for ipv6/ipv4/multiple DNS servers to timeout and work. TUNE-3796
- curl --connect-timeout 25 --max-time 172800 -C - --cacert "${CA_BUNDLE_DOWNLOAD}" -o "${file_name}.part" "${download_url}"
- res=$?
- if [ $res -ne 0 ]; then
- popd
- echo "Error: Download failure. ${res}"
- exit_status $res "Network"
- fi
- mv "${file_name}.part" "${file_name}"
- else
- echo "'${file_name}' already fully downloaded."
- fi
- popd
- echo "${update_image}"
- }
- function check_version()
- {
- if [ -f /proc/aether/devtype ]; then
- case $(cat /proc/aether/devtype) in
- (tiburon) firmware=FRM000104 ;;
- (salinas*) firmware=FRM000105 ;;
- (*) firmware=FRMxxxxxx ;;
- esac
- else
- firmware=$(echo $BUILD_VERSION | awk -F. '{print $1}')
- fi
- version=$(echo "${1}" | awk -F. '{print $1'})
- [ "$version" = "$firmware" ]
- return $?
- }
- function bad_update()
- {
- printf "%s does not appear to be a valid update file: %s\n" "${1}" "${2}" 1>&2
- # categorize this as a network error ... sysupdated will then try to redownload soon
- # if it was downloaded, remove the broken package
- if [ -n "${update_server}" ]; then
- invalid_image=true
- fi
- exit_status 1 "Network"
- }
- function verify_package()
- {
- # Verifying a package is a four step process
- # 0. Mount the package
- mkdir -p ${PKG_MOUNT}
- export METFS_PASSPHRASE='GyamY!ASDb5asBZ$Faf5(24qBAhasfg'
- # Not entirely portable...debian (and BusyBox) uses realpath, other distros use readlink
- metfs --mount ${PKG_MOUNT} --file "$(realpath ${update_image})" -r
- unset METFS_PASSPHRASE
- # 1. Make sure the inside file exists.
- if [ ! -r $PKG_MOUNT/$INSIDE_FILE ]; then
- bad_update ${update_image} "Mising $INSIDE_FILE in the interior of the update file"
- fi
- sign_cert="$PKG_MOUNT/cert.pem"
- # 2. Verify the signature inside the package comes from the cert in the package. Do not change the file names here
- pubkey_temp=$(mktemp -q -t pubkey.XXXXXX)
- openssl x509 -pubkey -noout -in "${sign_cert}" > ${pubkey_temp}
- cat "$PKG_MOUNT/rootfs.tar.gz" "$PKG_MOUNT/version.txt" "$PKG_MOUNT/pre-inst.sh" "$PKG_MOUNT/post-inst.sh" "$PKG_MOUNT/bootloader.img" "$PKG_MOUNT/bootloader.ver" | \
- openssl dgst -sha256 -verify "${pubkey_temp}" -signature "$PKG_MOUNT/signature.sha256"
- if [ $? -ne 0 ]; then
- bad_update ${update_image} "Package signature is not valid"
- fi
- # 2a. Verify the version inside the package matches the current one
- if ! check_version $(cat "$PKG_MOUNT/version.txt"); then
- bad_update ${update_image} "Version mismatch: this firmware cannot be loaded on this device"
- fi
- # 3. Verify the cert can be used for Code signing. This will lock out using a device cert
- openssl x509 -in "${sign_cert}" -noout -text -certopt no_serial,no_sigdump,no_issuer,no_subject,no_header,no_version,no_signame,no_validity,no_pubkey,no_aux | egrep -A1 '^ *X509v3 Extended Key Usage: critical$' | egrep -o '^ *Code Signing,'
- if [ $? -ne 0 ]; then
- bad_update ${update_image} "Certificate used to sign package does support code signing"
- fi
- # Check if we are in direct package mode and if so skip the verify to root chain so a developer build can be installed
- # 4. Verify the cert is properly signed by our root anchor only. This will
- # Openssl verify doesn't set its return code to non 0, so we have to use the contents of its output.
- # If everything passed, just "OK" wihtout quotes is returned. If anything failed, then more is returned
- # This check also makes sure the purpose is valid, but smimesign is not the best one, but only one we can
- # use with the verify command. We will check the extended purpose fields in a seperate call
- if [ -z "${no_cert_chain_verify}" ]
- then
- openssl verify -CAfile "${MCA_CERT_DIR}/aether_root.pem" "${sign_cert}"
- if [ $? -ne 0 ]; then
- bad_update ${update_image} "Certificate does not validate to a trusted root"
- fi
- fi
- return 0
- }
- function clean_part()
- {
- if [[ "${verbose}" ]]
- then
- printf "Erasing %s. Continue? " ${1}
- read yorn
- case $yorn in
- ([yY]*) : nothing
- ;;
- (*)
- printf "Aborting\n"
- exit_status 42 "User Abort"
- ;;
- esac
- fi
- mkfs.ext4 ${1} -L rootfsf${2}
- }
- function find_next_dev()
- {
- cur_root_dev=${1}
- next_part=${2}
- case $next_part in
- (f) partition=1
- ;;
- (a) partition=2
- ;;
- (b) partition=3
- ;;
- esac
- printf "%s" $cur_root_dev | sed -e 's/.$/'$partition'/'
- }
- function get_partition_and_dev()
- {
- factory=${1-0}
- part_id=$(sed -e 's/.* partition=\([abf]\).*/\1/' < /proc/cmdline)
- dev=$(sed -e 's/.* root=\([^ ]*\).*/\1/' < /proc/cmdline)
- case $part_id in
- (f) next_part=a
- ;;
- (a) next_part=b
- ;;
- (b) next_part=a
- ;;
- (*) printf "Current root partition could not be parsed from %s\n" $part_id 1>&2
- exit_status 2 "Configuration"
- ;;
- esac
- if [[ "${factory}" = 1 ]]
- then
- next_part=f
- fi
- next_dev=$(find_next_dev $dev $next_part)
- if [[ "${part_id}" = "${next_part}" ]]
- then
- printf "Cannot overwrite current partition (%s)\n" ${part_id} 1>&2
- exit_status 2 "Configuration"
- fi
- if [[ "${verbose}" ]]
- then
- printf "Updating from partion %s on %s to partition %s on %s\n" $part_id $dev $next_part $next_dev 1>&2
- fi
- printf "%s %s" $next_dev $next_part
- }
- function update_bootloader()
- {
- if [ -e /proc/cmdline -a -e ${PKG_MOUNT}/bootloader.ver -a -e ${PKG_MOUNT}/bootloader.img ]; then
- uboot_cur_ver=$(sed -e 's/.* uboot_ver="\([^"]*\).*/\1/' < /proc/cmdline)
- uboot_dev=$(sed -e 's/.* uboot_dev=\([^ ]*\).*/\1/' < /proc/cmdline)
- uboot_new_ver=$(< ${PKG_MOUNT}/bootloader.ver)
- if [ "${uboot_cur_ver}" = "${uboot_new_ver}" ]; then
- echo "Bootloader versions match, not upgrading."
- return 0
- fi
- echo "Bootloader needs to be updated to '${uboot_new_ver}' on device '${uboot_dev}'"
- dd if=${PKG_MOUNT}/bootloader.img of=${uboot_dev} bs=512 conv=notrunc seek=2
- else
- echo "Couldn't determine if bootloader needs to be updated"
- return 1
- fi
- }
- function get_current_version()
- {
- grep BUILD_VERSION /etc/os-release | awk -F= '{print $2}'
- }
- function get_alt_version()
- {
- dev=${1}
- # 99% of the time, we'll run this script and not actually update the system. However, everytime we request
- # an update and get the alternate version, we mount the alternate partition, which causes this kernel message
- # to be printed to the console:
- # EXT4-fs (mmcblk0p3): mounted filesystem with ordered data mode. Opts: (null)
- # Here we temporarily disable kernel printk()s in order to suppress that spam.
- old=$(cat /proc/sys/kernel/printk)
- echo "4 1 1 7" > /proc/sys/kernel/printk
- mount -t ext4 -oro $dev ${PART_MOUNT}
- set -o pipefail
- grep BUILD_VERSION ${PART_MOUNT}/etc/os-release | awk -F= '{print $2}' || echo unknown
- set +o pipefail
- umount ${PART_MOUNT}
- echo ${old} > /proc/sys/kernel/printk
- }
- function is_official_build()
- {
- # Expected path to /etc/os-release
- osFile=${1}
- # rawVersion will be something like
- # FRM000102.00.02.20131203182305-npelis <-- developer build
- # FRM000102.01.01.0207 <-- official build
- # According to Aron, official builds shall always be padded four digit numbers
- # while developer builds shall always be date/time stamps + usernames. So this
- # is how we differentiate between official vs developer builds
- rawVersion=$(grep BUILD_VERSION ${osFile} | sed 's/BUILD_VERSION=//')
- buildNum=$(echo ${rawVersion} | sed 's/.*\.\(.*\)$/\1/')
- if [ ${#buildNum} -eq 0 ]; then
- echo -1
- elif [ ${#buildNum} -eq 4 ]; then
- echo 1
- else
- echo 0
- fi
- }
- function falsify_build()
- {
- # rawVersion will be something like
- # FRM000102.00.02.20131203182305-npelis <-- developer private build
- # FRM000102.01.01.0207 <-- developer public build
- # FRM000102.01.02.0207 <-- official build
- # NOTE falsified build numbers pass the "is_official_build" test
- rawVersion=${1}
- # Expected path to /etc/os-release
- #osFile=${1}
- #rawVersion=$(grep BUILD_VERSION ${osFile} | sed 's/BUILD_VERSION=//')
- newVersion=$(echo ${rawVersion} | awk -F. '{ printf "%s.00.00.0000\n", $1}')
- echo ${newVersion}
- }
- function usage()
- {
- printf "%s [-d|--download] [-n|--no-cert-chain-verify] [-o|--overwrite-factory] [-f|--force] [-p package_file] [-s update_server] [-v|--verbose[=level]] [-t status_file]\n" ${0} 1>&2
- printf " Note: -p (or --pkg) and -s (or --server) are mutually exclusive\n" 1>&2
- printf " Note: -d (or --download) Will just download the update file, but not install it\n" 1>&2
- printf " status_file causes the result of the operation to be written to the specified file\n" 1>&2
- exit_status 1 "Usage"
- }
- function main()
- {
- OPTIONS=$(getopt -o dfnot:p:s:v::h --long download,no-cert-chain-verify,status_file:,force,pkg:,server:,verbose::,help -n ${0} -- ${@+"$@"})
- if [ $? != 0 ] ; then printf "Bad options" >&2 ; exit_status 1 "Usage"; fi
- force=0
- factory=0
- download_only=0
- update_server="${server}"
- program=$(basename $0)
- eval set -- "$OPTIONS"
- while true ; do
- case "$1" in
- (-d|--download)
- download_only=1
- shift
- ;;
- (-f|--force)
- force=1
- shift
- ;;
- (-n|--no-cert-chain-verify)
- case "$program" in
- (sysupdate|sysupdate.sh)
- no_cert_chain_verify=true
- ;;
- (*)
- printf "Permission denied\n" 1>&2
- exit_status 1 "Usage"
- ;;
- esac
- shift
- ;;
- (-o|--overwrite-factory)
- factory=1
- shift
- ;;
- (-p|--pkg)
- update_image=$2
- update_server=
- shift 2
- ;;
- (-s|--server)
- update_server=$2
- update_image=
- shift 2
- ;;
- (-v|--verbose)
- case "${2}" in
- ([0-9]*)
- verbose=$2
- ;;
- (*)
- verbose=$((verbose+1))
- ;;
- esac
- shift 2;;
- (-t|--status_file)
- status_file=$2
- shift 2
- ;;
- (-h|--help)
- usage
- shift ;;
- (--) shift
- break ;;
- (*) echo -- "'$1' unknown argument"
- usage
- exit_status 1 "Usage"
- esac
- done
- if [[ $EUID -ne 0 ]]; then
- echo "You must be root to run this script"
- exit_status 1 "Root_fail"
- fi
- if [ -s "${update_image}" -a -s "${update_server}" ]
- then
- echo -- "--pkg and --server are mutually exclusive" 1>&2
- usage
- exit_status 1 "Usage"
- fi
- # Check for a set, but nonexistent package update file. Bail out.
- if [ "x${update_image}" != "x" -a ! -e "${update_image}" ]
- then
- echo "Error: The package file ${update_image} does not exist." 1>&2
- exit_status 1 "Usage"
- fi
- set -- $(get_partition_and_dev $factory)
- next_dev=$1
- next_part=$2
- alt_version=$(get_alt_version ${next_dev})
- current_version=$(get_current_version)
- official_build=$(is_official_build /etc/os-release)
- factory_dev=$(find_next_dev $(sed -e 's/.* root=\([^ ]*\).*/\1/' < /proc/cmdline) f)
- factory_version=$(get_alt_version ${factory_dev})
- if [ ${force} -eq 1 ]; then
- new_alt_version=$(falsify_build ${alt_version})
- new_current_version=$(falsify_build ${current_version})
- echo "Info: Falsifying build numbers. Actual builds on current/alternate partitions are ${current_version}/${alt_version}."
- echo "Info: Falsified builds are ${new_current_version}/${new_alt_version}"
- current_version=${new_current_version}
- alt_version=${new_alt_version}
- official_build=1
- fi
- # if the current build is not an official one, only proceed if this is
- # either forced (-f) or from a package (-p),
- if [ -z "${update_image}" ]; then
- if [ ${official_build} -eq 1 ]; then
- download ${current_version} ${alt_version} ${factory_version}
- else
- echo "Error: current build is not official. Can only update with -f" 1>&2
- exit_status 1 "Usage"
- fi
- fi
- if [ ${download_only} -eq 1 ]; then
- echo "Download of update finished"
- exit_status 0 "Download Only"
- fi
- if ! batt --ac > /dev/null
- then
- # check that the system has as least 10% battery life before proceeding,
- # unless forced through with the -f flag
- level=$(batt -m 10)
- if [ $? -ne 0 -a ${force} -ne 1 ]; then
- printf "Battery must be connected, and must be at minimum 10%% charge to continue (actually at %s)\n" ${level} 1>&2
- exit_status 1 "Battery"
- fi
- fi
- # update_image should now be set
- if verify_package $update_image
- then
- $PKG_MOUNT/pre-inst.sh
- case $? in
- (0)
- clean_part $next_dev $next_part
- sync; sync;
- mount -t ext4 $next_dev ${PART_MOUNT}
- ;;
- (1)
- mount -t ext4 $next_dev ${PART_MOUNT}
- ;;
- (*)
- printf "pre-inst.sh: failed\n" 1>&2
- exit_status 1 "Install"
- ;;
- esac
- [ "${verbose-0}" -gt 0 ] && vflag=v
- tar="gunzip -c $PKG_MOUNT/rootfs.tar.gz | tar x${vflag}f -"
- echo ${tar}
- (set -e;cd ${PART_MOUNT} && eval ${tar})
- if [ $? -ne 0 ]; then
- printf "Failed to untar contents of package, aborting system update\n" 1>&2
- exit_status 1 "Install"
- fi
- update_bootloader
- $PKG_MOUNT/post-inst.sh
- new_version=$(eval $(grep BUILD_VERSION $PART_MOUNT/etc/os-release);echo $BUILD_VERSION)
- sync
- umount ${PART_MOUNT}
- update_complete="y"
- echo "Sync'ing to disk"
- sync
- echo "Setting MCU flags to switch to partition '$next_part' at the next boot"
- if bootflags -s -p $next_part -f 0
- then
- need_reboot=1
- else
- printf "Update installed, but unable to access mcu\n" 1>&2
- exit_status 42 "MCU"
- fi
- fi
- }
- # Create a pid file
- if [ -f ${LOCK_FILE} ]; then
- otherpid=$(cat ${LOCK_FILE})
- if [ -n "${otherpid}" ] && grep -q "sysupdate" /proc/${otherpid}/cmdline; then
- echo "System Update already running, quitting this copy"
- exit_status 1 "Duplicate"
- else
- echo "Removing stale lock file ${LOCK_FILE}"
- # do not use -f ... if the lock file is owned by someone else, this shold fail
- rm ${LOCK_FILE} || exit_status 1 "PIDFail"
- fi
- fi
- echo "$$" > ${LOCK_FILE} || exit_status 1 "PIDFail"
- trap clean EXIT HUP INT QUIT TERM
- main ${@+"$@"}
Add Comment
Please, Sign In to add comment