diff options
Diffstat (limited to 'packaging/installer/netdata-updater.sh')
-rwxr-xr-x | packaging/installer/netdata-updater.sh | 517 |
1 files changed, 364 insertions, 153 deletions
diff --git a/packaging/installer/netdata-updater.sh b/packaging/installer/netdata-updater.sh index 6609edd5..10835cd0 100755 --- a/packaging/installer/netdata-updater.sh +++ b/packaging/installer/netdata-updater.sh @@ -1,162 +1,372 @@ #!/usr/bin/env bash -#shellcheck disable=SC2164 -# + # Netdata updater utility # # Variables needed by script: # - PATH # - CFLAGS +# - LDFLAGS +# - IS_NETDATA_STATIC_BINARY # - NETDATA_CONFIGURE_OPTIONS -# - REINSTALL_COMMAND +# - REINSTALL_OPTIONS # - NETDATA_TARBALL_URL # - NETDATA_TARBALL_CHECKSUM_URL # - NETDATA_TARBALL_CHECKSUM # - NETDATA_PREFIX / NETDATA_LIB_DIR (After 1.16.1 we will only depend on lib dir) # -# Copyright: SPDX-License-Identifier: GPL-3.0-or-later +# Optional environment options: +# +# - TMPDIR (set to a usable temporary directory) +# - NETDATA_NIGHTLIES_BASEURL (set the base url for downloading the dist tarball) +# +# Copyright: 2018-2020 Netdata Inc. +# SPDX-License-Identifier: GPL-3.0-or-later # # Author: Paweł Krupa <paulfantom@gmail.com> # Author: Pavlos Emm. Katsoulakis <paul@netdata.cloud> +# Author: Austin S. Hemmelgarn <austin@netdata.cloud> + +set -e + +script_dir="$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd -P)" + +if [ -x "${script_dir}/netdata-updater" ]; then + script_source="${script_dir}/netdata-updater" +else + script_source="${script_dir}/netdata-updater.sh" +fi info() { - echo >&3 "$(date) : INFO: " "${@}" + echo >&3 "$(date) : INFO: " "${@}" } error() { - echo >&3 "$(date) : ERROR: " "${@}" + echo >&3 "$(date) : ERROR: " "${@}" } +: "${ENVIRONMENT_FILE:=THIS_SHOULD_BE_REPLACED_BY_INSTALLER_SCRIPT}" + +if [ "${ENVIRONMENT_FILE}" == "THIS_SHOULD_BE_REPLACED_BY_INSTALLER_SCRIPT" ]; then + if [ -r "${script_dir}/../../../etc/netdata/.environment" ]; then + ENVIRONMENT_FILE="${script_dir}/../../../etc/netdata/.environment" + elif [ -r "/etc/netdata/.environment" ]; then + ENVIRONMENT_FILE="/etc/netdata/.environment" + elif [ -r "/opt/netdata/etc/netdata/.environment" ]; then + ENVIRONMENT_FILE="/opt/netdata/etc/netdata/.environment" + else + envpath="$(find / -type d \( -path /sys -o -path /proc -o -path /dev \) -prune -false -o -path '*netdata/.environment' -type f 2> /dev/null | head -n 1)" + if [ -r "${envpath}" ]; then + ENVIRONMENT_FILE="${envpath}" + else + error "Cannot find environment file, unable to update." + exit 1 + fi + fi +fi + safe_sha256sum() { - # Within the contexct of the installer, we only use -c option that is common between the two commands - # We will have to reconsider if we start non-common options - if command -v sha256sum >/dev/null 2>&1; then - sha256sum $@ - elif command -v shasum >/dev/null 2>&1; then - shasum -a 256 $@ - else - fatal "I could not find a suitable checksum binary to use" - fi + # Within the contexct of the installer, we only use -c option that is common between the two commands + # We will have to reconsider if we start non-common options + if command -v sha256sum > /dev/null 2>&1; then + sha256sum "$@" + elif command -v shasum > /dev/null 2>&1; then + shasum -a 256 "$@" + else + fatal "I could not find a suitable checksum binary to use" + fi } # this is what we will do if it fails (head-less only) fatal() { - error "FAILED TO UPDATE NETDATA : ${1}" + error "FAILED TO UPDATE NETDATA : ${1}" + exit 1 +} + +cleanup() { + if [ -n "${logfile}" ]; then + cat >&2 "${logfile}" + rm "${logfile}" + fi + + if [ -n "$ndtmpdir" ] && [ -d "$ndtmpdir" ]; then + rm -rf "$ndtmpdir" + fi +} - if [ -n "${logfile}" ]; then - cat >&2 "${logfile}" - rm "${logfile}" - fi - exit 1 +_cannot_use_tmpdir() { + local testfile ret + testfile="$(TMPDIR="${1}" mktemp -q -t netdata-test.XXXXXXXXXX)" + ret=0 + + if [ -z "${testfile}" ] ; then + return "${ret}" + fi + + if printf '#!/bin/sh\necho SUCCESS\n' > "${testfile}" ; then + if chmod +x "${testfile}" ; then + if [ "$("${testfile}")" = "SUCCESS" ] ; then + ret=1 + fi + fi + fi + + rm -f "${testfile}" + return "${ret}" } create_tmp_directory() { - # Check if tmp is mounted as noexec - if grep -Eq '^[^ ]+ /tmp [^ ]+ ([^ ]*,)?noexec[, ]' /proc/mounts; then - pattern="$(pwd)/netdata-updater-XXXXXX" - else - pattern="/tmp/netdata-updater-XXXXXX" - fi - - mktemp -d "$pattern" + if [ -n "${NETDATA_TMPDIR_PATH}" ]; then + TMPDIR="${NETDATA_TMPDIR_PATH}" + else + if [ -z "${NETDATA_TMPDIR}" ] || _cannot_use_tmpdir "${NETDATA_TMPDIR}" ; then + if [ -z "${TMPDIR}" ] || _cannot_use_tmpdir "${TMPDIR}" ; then + if _cannot_use_tmpdir /tmp ; then + if _cannot_use_tmpdir "${PWD}" ; then + echo >&2 + echo >&2 "Unable to find a usable temprorary directory. Please set \$TMPDIR to a path that is both writable and allows execution of files and try again." + exit 1 + else + TMPDIR="${PWD}" + fi + else + TMPDIR="/tmp" + fi + fi + else + TMPDIR="${NETDATA_TMPDIR}" + fi + + mktemp -d -t netdata-updater-XXXXXXXXXX + fi +} + +_safe_download() { + url="${1}" + dest="${2}" + if command -v curl > /dev/null 2>&1; then + curl -sSL --connect-timeout 10 --retry 3 "${url}" > "${dest}" + return $? + elif command -v wget > /dev/null 2>&1; then + wget -T 15 -O - "${url}" > "${dest}" + return $? + else + return 255 + fi } download() { - url="${1}" - dest="${2}" - if command -v curl >/dev/null 2>&1; then - curl -sSL --connect-timeout 10 --retry 3 "${url}" >"${dest}" || fatal "Cannot download ${url}" - elif command -v wget >/dev/null 2>&1; then - wget -T 15 -O - "${url}" >"${dest}" || fatal "Cannot download ${url}" - else - fatal "I need curl or wget to proceed, but neither is available on this system." - fi + url="${1}" + dest="${2}" + + _safe_download "${url}" "${dest}" + ret=$? + + if [ ${ret} -eq 0 ]; then + return 0 + elif [ ${ret} -eq 255 ]; then + fatal "I need curl or wget to proceed, but neither is available on this system." + else + fatal "Cannot download ${url}" + fi +} + +newer_commit_date() { + echo >&3 "Checking if a newer version of the updater script is available." + + if command -v jq > /dev/null 2>&1; then + commit_date="$(_safe_download "https://api.github.com/repos/netdata/netdata/commits?path=packaging%2Finstaller%2Fnetdata-updater.sh&page=1&per_page=1" /dev/stdout | jq '.[0].commit.committer.date' | tr -d '"')" + elif command -v python > /dev/null 2>&1;then + commit_date="$(_safe_download "https://api.github.com/repos/netdata/netdata/commits?path=packaging%2Finstaller%2Fnetdata-updater.sh&page=1&per_page=1" /dev/stdout | python -c 'from __future__ import print_function;import sys,json;print(json.load(sys.stdin)[0]["commit"]["committer"]["date"])')" + elif command -v python3 > /dev/null 2>&1;then + commit_date="$(_safe_download "https://api.github.com/repos/netdata/netdata/commits?path=packaging%2Finstaller%2Fnetdata-updater.sh&page=1&per_page=1" /dev/stdout | python3 -c 'from __future__ import print_function;import sys,json;print(json.load(sys.stdin)[0]["commit"]["committer"]["date"])')" + fi + + if [ -z "${commit_date}" ] ; then + commit_date="9999-12-31T23:59:59Z" + fi + + if [ -e "${script_source}" ]; then + script_date="$(date -r "${script_source}" +%s)" + else + script_date="$(date +%s)" + fi + + [ "$(date -d "${commit_date}" +%s)" -ge "${script_date}" ] +} + +self_update() { + if [ -z "${NETDATA_NO_UPDATER_SELF_UPDATE}" ] && newer_commit_date; then + echo >&3 "Downloading newest version of updater script." + + ndtmpdir=$(create_tmp_directory) + cd "$ndtmpdir" || exit 1 + + if _safe_download "https://raw.githubusercontent.com/netdata/netdata/master/packaging/installer/netdata-updater.sh" ./netdata-updater.sh; then + chmod +x ./netdata-updater.sh || exit 1 + export ENVIRONMENT_FILE="${ENVIRONMENT_FILE}" + exec ./netdata-updater.sh --not-running-from-cron --no-self-update --tmpdir-path "$(pwd)" + else + echo >&3 "Failed to download newest version of updater script, continuing with current version." + fi + fi +} + +parse_version() { + r="${1}" + if echo "${r}" | grep -q '^v.*'; then + # shellcheck disable=SC2001 + # XXX: Need a regex group subsitutation here. + r="$(echo "${r}" | sed -e 's/^v\(.*\)/\1/')" + fi + + read -r -a p <<< "$(echo "${r}" | tr '-' ' ')" + + v="${p[0]}" + b="${p[1]}" + _="${p[2]}" # ignore the SHA + + if [[ ! "${b}" =~ ^[0-9]+$ ]]; then + b="0" + fi + + read -r -a pp <<< "$(echo "${v}" | tr '.' ' ')" + printf "%03d%03d%03d%05d" "${pp[0]}" "${pp[1]}" "${pp[2]}" "${b}" +} + +get_latest_version() { + local latest + if [ "${RELEASE_CHANNEL}" == "stable" ]; then + latest="$(download "https://api.github.com/repos/netdata/netdata/releases/latest" /dev/stdout | grep tag_name | cut -d'"' -f4)" + else + latest="$(download "$NETDATA_NIGHTLIES_BASEURL/latest-version.txt" /dev/stdout)" + fi + parse_version "$latest" } set_tarball_urls() { - local extension="tar.gz" - - if [ ! -z "${NETDATA_LOCAL_TARBAL_OVERRIDE}" ]; then - info "Not fetching remote tarballs, local override was given" - return - fi - - if [ "$2" == "yes" ]; then - extension="gz.run" - fi - - if [ "$1" = "stable" ]; then - local latest - # Simple version - # latest="$(curl -sSL https://api.github.com/repos/netdata/netdata/releases/latest | grep tag_name | cut -d'"' -f4)" - latest="$(download "https://api.github.com/repos/netdata/netdata/releases/latest" /dev/stdout | grep tag_name | cut -d'"' -f4)" - export NETDATA_TARBALL_URL="https://github.com/netdata/netdata/releases/download/$latest/netdata-$latest.${extension}" - export NETDATA_TARBALL_CHECKSUM_URL="https://github.com/netdata/netdata/releases/download/$latest/sha256sums.txt" - else - export NETDATA_TARBALL_URL="https://storage.googleapis.com/netdata-nightlies/netdata-latest.${extension}" - export NETDATA_TARBALL_CHECKSUM_URL="https://storage.googleapis.com/netdata-nightlies/sha256sums.txt" - fi + local extension="tar.gz" + + if [ "$2" == "yes" ]; then + extension="gz.run" + fi + + if [ "$1" = "stable" ]; then + local latest + # Simple version + latest="$(download "https://api.github.com/repos/netdata/netdata/releases/latest" /dev/stdout | grep tag_name | cut -d'"' -f4)" + export NETDATA_TARBALL_URL="https://github.com/netdata/netdata/releases/download/$latest/netdata-$latest.${extension}" + export NETDATA_TARBALL_CHECKSUM_URL="https://github.com/netdata/netdata/releases/download/$latest/sha256sums.txt" + else + export NETDATA_TARBALL_URL="$NETDATA_NIGHTLIES_BASEURL/netdata-latest.${extension}" + export NETDATA_TARBALL_CHECKSUM_URL="$NETDATA_NIGHTLIES_BASEURL/sha256sums.txt" + fi } update() { - [ -z "${logfile}" ] && info "Running on a terminal - (this script also supports running headless from crontab)" - - RUN_INSTALLER=0 - tmpdir=$(create_tmp_directory) - cd "$tmpdir" - - if [ -z "${NETDATA_LOCAL_TARBAL_OVERRIDE}" ]; then - download "${NETDATA_TARBALL_CHECKSUM_URL}" "${tmpdir}/sha256sum.txt" >&3 2>&3 - if [[ -n "${NETDATA_TARBALL_CHECKSUM}" ]] && grep "${NETDATA_TARBALL_CHECKSUM}" sha256sum.txt >&3 2>&3; then - info "Newest version is already installed" - else - download "${NETDATA_TARBALL_URL}" "${tmpdir}/netdata-latest.tar.gz" - if ! grep netdata-latest.tar.gz sha256sum.txt | safe_sha256sum -c - >&3 2>&3; then - fatal "Tarball checksum validation failed. Stopping netdata upgrade and leaving tarball in ${tmpdir}" - fi - NEW_CHECKSUM="$(safe_sha256sum netdata-latest.tar.gz 2>/dev/null| cut -d' ' -f1)" - tar -xf netdata-latest.tar.gz >&3 2>&3 - rm netdata-latest.tar.gz >&3 2>&3 - cd netdata-* - RUN_INSTALLER=1 - fi - else - info "!!Local tarball override detected!! - Entering directory ${NETDATA_LOCAL_TARBAL_OVERRIDE} for installation, not downloading anything" - RUN_INSTALLER=1 - cd ${NETDATA_LOCAL_TARBAL_OVERRIDE} - fi - - - # We got the sources, run the update now - if [ ${RUN_INSTALLER} -eq 1 ]; then - # signal netdata to start saving its database - # this is handy if your database is big - pids=$(pidof netdata) - do_not_start= - if [ -n "${pids}" ]; then - #shellcheck disable=SC2086 - kill -USR1 ${pids} - else - # netdata is currently not running, so do not start it after updating - do_not_start="--dont-start-it" - fi - - info "Re-installing netdata..." - eval "${REINSTALL_COMMAND} --dont-wait ${do_not_start}" >&3 2>&3 || fatal "FAILED TO COMPILE/INSTALL NETDATA" - - # We no longer store checksum info here. but leave this so that we clean up all environment files upon next update. - sed -i '/NETDATA_TARBALL/d' "${ENVIRONMENT_FILE}" - - info "Updating tarball checksum info" - echo "${NEW_CHECKSUM}" > "${NETDATA_LIB_DIR}/netdata.tarball.checksum" - fi - - rm -rf "${tmpdir}" >&3 2>&3 - [ -n "${logfile}" ] && rm "${logfile}" && logfile= - return + [ -z "${logfile}" ] && info "Running on a terminal - (this script also supports running headless from crontab)" + + RUN_INSTALLER=0 + ndtmpdir=$(create_tmp_directory) + cd "$ndtmpdir" || exit 1 + + download "${NETDATA_TARBALL_CHECKSUM_URL}" "${ndtmpdir}/sha256sum.txt" >&3 2>&3 + + current_version="$(command -v netdata > /dev/null && parse_version "$(netdata -v | cut -f 2 -d ' ')")" + latest_version="$(get_latest_version)" + + # If we can't get the current version for some reason assume `0` + current_version="${current_version:-0}" + + # If we can't get the latest version for some reason assume `0` + latest_version="${latest_version:-0}" + + info "Current Version: ${current_version}" + info "Latest Version: ${latest_version}" + + if [ "${latest_version}" -gt 0 ] && [ "${current_version}" -gt 0 ] && [ "${current_version}" -ge "${latest_version}" ]; then + info "Newest version (current=${current_version} >= latest=${latest_version}) is already installed" + elif [ -n "${NETDATA_TARBALL_CHECKSUM}" ] && grep "${NETDATA_TARBALL_CHECKSUM}" sha256sum.txt >&3 2>&3; then + info "Newest version is already installed" + else + download "${NETDATA_TARBALL_URL}" "${ndtmpdir}/netdata-latest.tar.gz" + if ! grep netdata-latest.tar.gz sha256sum.txt | safe_sha256sum -c - >&3 2>&3; then + fatal "Tarball checksum validation failed. Stopping netdata upgrade and leaving tarball in ${ndtmpdir}\nUsually this is a result of an older copy of the tarball or checksum file being cached somewhere upstream and can be resolved by retrying in an hour." + fi + NEW_CHECKSUM="$(safe_sha256sum netdata-latest.tar.gz 2> /dev/null | cut -d' ' -f1)" + tar -xf netdata-latest.tar.gz >&3 2>&3 + rm netdata-latest.tar.gz >&3 2>&3 + cd netdata-* || exit 1 + RUN_INSTALLER=1 + cd "${NETDATA_LOCAL_TARBAL_OVERRIDE}" || exit 1 + fi + + # We got the sources, run the update now + if [ ${RUN_INSTALLER} -eq 1 ]; then + # signal netdata to start saving its database + # this is handy if your database is big + possible_pids=$(pidof netdata) + do_not_start= + if [ -n "${possible_pids}" ]; then + read -r -a pids_to_kill <<< "${possible_pids}" + kill -USR1 "${pids_to_kill[@]}" + else + # netdata is currently not running, so do not start it after updating + do_not_start="--dont-start-it" + fi + + if [ -n "${NETDATA_SELECTED_DASHBOARD}" ]; then + env="NETDATA_SELECTED_DASHBOARD=${NETDATA_SELECTED_DASHBOARD}" + fi + + if [ ! -x ./netdata-installer.sh ]; then + if [ "$(find . -mindepth 1 -maxdepth 1 -type d | wc -l)" -eq 1 ] && [ -x "$(find . -mindepth 1 -maxdepth 1 -type d)/netdata-installer.sh" ]; then + cd "$(find . -mindepth 1 -maxdepth 1 -type d)" || exit 1 + fi + fi + + info "Re-installing netdata..." + eval "${env} ./netdata-installer.sh ${REINSTALL_OPTIONS} --dont-wait ${do_not_start}" >&3 2>&3 || fatal "FAILED TO COMPILE/INSTALL NETDATA" + + # We no longer store checksum info here. but leave this so that we clean up all environment files upon next update. + sed -i '/NETDATA_TARBALL/d' "${ENVIRONMENT_FILE}" + + info "Updating tarball checksum info" + echo "${NEW_CHECKSUM}" > "${NETDATA_LIB_DIR}/netdata.tarball.checksum" + fi + + rm -rf "${ndtmpdir}" >&3 2>&3 + [ -n "${logfile}" ] && rm "${logfile}" && logfile= + + return 0 } -# Usually stored in /etc/netdata/.environment -: "${ENVIRONMENT_FILE:=THIS_SHOULD_BE_REPLACED_BY_INSTALLER_SCRIPT}" +logfile= +ndtmpdir= + +trap cleanup EXIT + +while [ -n "${1}" ]; do + if [ "${1}" = "--not-running-from-cron" ]; then + NETDATA_NOT_RUNNING_FROM_CRON=1 + shift 1 + elif [ "${1}" = "--no-updater-self-update" ]; then + NETDATA_NO_UPDATER_SELF_UPDATE=1 + shift 1 + elif [ "${1}" = "--tmpdir-path" ]; then + NETDATA_TMPDIR_PATH="${2}" + shift 2 + else + break + fi +done + +# Random sleep to aileviate stampede effect of Agents upgrading +# and disconnecting/reconnecting at the same time (or near to). +# But only we're not a controlling terminal (tty) +# Randomly sleep between 1s and 60m +if [ ! -t 1 ] && [ -z "${NETDATA_NOT_RUNNING_FROM_CRON}" ]; then + sleep $(((RANDOM % 3600) + 1)) +fi # shellcheck source=/dev/null source "${ENVIRONMENT_FILE}" || exit 1 @@ -167,51 +377,52 @@ export NETDATA_LIB_DIR="${NETDATA_LIB_DIR:-${NETDATA_PREFIX}/var/lib/netdata}" # Source the tarbal checksum, if not already available from environment (for existing installations with the old logic) [[ -z "${NETDATA_TARBALL_CHECKSUM}" ]] && [[ -f ${NETDATA_LIB_DIR}/netdata.tarball.checksum ]] && NETDATA_TARBALL_CHECKSUM="$(cat "${NETDATA_LIB_DIR}/netdata.tarball.checksum")" +# Grab the nightlies baseurl (defaulting to our Google Storage bucket) +export NETDATA_NIGHTLIES_BASEURL="${NETDATA_NIGHTLIES_BASEURL:-https://storage.googleapis.com/netdata-nightlies}" + if [ "${INSTALL_UID}" != "$(id -u)" ]; then - fatal "You are running this script as user with uid $(id -u). We recommend to run this script as root (user with uid 0)" + fatal "You are running this script as user with uid $(id -u). We recommend to run this script as root (user with uid 0)" fi -logfile= if [ -t 2 ]; then - # we are running on a terminal - # open fd 3 and send it to stderr - exec 3>&2 + # we are running on a terminal + # open fd 3 and send it to stderr + exec 3>&2 else - # we are headless - # create a temporary file for the log - logfile=$(mktemp ${logfile}/netdata-updater.log.XXXXXX) - # open fd 3 and send it to logfile - exec 3>"${logfile}" + # we are headless + # create a temporary file for the log + logfile="$(mktemp "${logfile}"/netdata-updater.log.XXXXXX)" + # open fd 3 and send it to logfile + exec 3> "${logfile}" fi +self_update + set_tarball_urls "${RELEASE_CHANNEL}" "${IS_NETDATA_STATIC_BINARY}" if [ "${IS_NETDATA_STATIC_BINARY}" == "yes" ]; then - TMPDIR="$(create_tmp_directory)" - PREVDIR="$(pwd)" - - echo >&2 "Entering ${TMPDIR}" - cd "${TMPDIR}" - - download "${NETDATA_TARBALL_CHECKSUM_URL}" "${TMPDIR}/sha256sum.txt" - download "${NETDATA_TARBALL_URL}" "${TMPDIR}/netdata-latest.gz.run" - if ! grep netdata-latest.gz.run "${TMPDIR}/sha256sum.txt" | safe_sha256sum -c - >/dev/null 2>&1; then - fatal "Static binary checksum validation failed. Stopping netdata installation and leaving binary in ${TMPDIR}" - fi - - # Do not pass any options other than the accept, for now - sh "${TMPDIR}/netdata-latest.gz.run" --accept - - #shellcheck disable=SC2181 - if [ $? -eq 0 ]; then - rm -r "${TMPDIR}" - else - echo >&2 "NOTE: did not remove: ${TMPDIR}" - fi - echo >&2 "Switching back to ${PREVDIR}" - cd "${PREVDIR}" + ndtmpdir="$(create_tmp_directory)" + PREVDIR="$(pwd)" + + echo >&2 "Entering ${ndtmpdir}" + cd "${ndtmpdir}" || exit 1 + + download "${NETDATA_TARBALL_CHECKSUM_URL}" "${ndtmpdir}/sha256sum.txt" + download "${NETDATA_TARBALL_URL}" "${ndtmpdir}/netdata-latest.gz.run" + if ! grep netdata-latest.gz.run "${ndtmpdir}/sha256sum.txt" | safe_sha256sum -c - > /dev/null 2>&1; then + fatal "Static binary checksum validation failed. Stopping netdata installation and leaving binary in ${ndtmpdir}\nUsually this is a result of an older copy of the file being cached somehere and can be resolved by simply retrying in an hour." + fi + + # Do not pass any options other than the accept, for now + # shellcheck disable=SC2086 + if sh "${ndtmpdir}/netdata-latest.gz.run" --accept -- ${REINSTALL_OPTIONS}; then + rm -r "${ndtmpdir}" + else + echo >&2 "NOTE: did not remove: ${ndtmpdir}" + fi + echo >&2 "Switching back to ${PREVDIR}" + cd "${PREVDIR}" || exit 1 else - # the installer updates this script - so we run and exit in a single line - update && exit 0 + # the installer updates this script - so we run and exit in a single line + update && exit 0 fi - |