#!/bin/bash
################################################################################
# Name:
#   cloudbackup-updater.setup
# Description:
#   Detects the platform running the updater and invokes the appropriate modules
#   for the platform. If os.data is detected in the same folder, it is assumed
#   that this is running in tar distro mode; otherwise, it is assumed that this
#   is running in normal update mode. NOTE: ServerMill will call this script in
#   the tarball with --user and not --username.
# Requires parameters:
#   --help (h) -- prints this help text
#   --version (v) -- prints the version of this script
#   --shutdown (s) -- tries to find cloudbackup-updater and shut it down
#   --configure (c) --username (u) <username> --apikey (k) <apiKey> --flavor (f)
#     <flavor> --datacenter (t) <dataCenter> --apihost (a) <apiDrivesrvr>
#     --force (o)
# Requires environment variables:
#   TRACE - if defined turns on function tracing
# TODO: WARNING! THE --user PARAMETER PASSED TO THIS SCRIPT IS INCOMPATIBLE WITH --username PASSED TO LEGACY BINARY UPDATER!!!!!!! ADD --user TO LEGACY GOING FORWARD

################################################################################
# Description:
#   Console logging for updater scripts. Wraps the message in a banner.
# Requires parameters:
#   $1 - log message
# Requires environment variables:
#   N/A
# Sets environment variables:
#   N/A
# Returns:
#   String banner. WARNING! Can't call anything else from here that prints to
#   stdout!
export installlogname=/var/log/cbu-installer.log
function bannerfun
{
  size=${#1}
  sizetotal=`expr $size + 6`
  for ((i=1; i<=$sizetotal; i++)) do echo -n =; done
  echo ''
  for ((i=1; i<=$sizetotal; i++)) do echo -n =; done
  echo ''
  echo "== $1 =="
  for ((i=1; i<=$sizetotal; i++)) do echo -n =; done
  echo ''
  for ((i=1; i<=$sizetotal; i++)) do echo -n =; done
  echo ''
  return 0
}

################################################################################
# Description:
#   Logging for updater scripts. If we are logging a function entry, reflect
#   that to stdout. If we are logging a fatal error, reflect that to stderr.
# Requires parameters:
#   $1 - line number
#   $2 - log message
# Requires environment variables:
#   installlogname - log file name
# Sets environment variables:
#   theapikey - api key to scrub from all messages
# Returns:
#   Return code 0 or exit code 1
export installlogname=/var/log/cbu-installer.log
function logmsg
{
  linenumber=$1; [[ ! -z "${linenumber}" ]] && linenumber="(${linenumber})"
  logmessage=$2
  [[ ! -z "${theapikey}" ]] && [[ "${logmessage}" =~ "${theapikey}" ]] && logmessage=${logmessage/$theapikey/REDACTED}
  echo "$( date '+%F %H:%M:%S' ) [$$] ${thescriptname}${linenumber}: ${logmessage}" >> ${installlogname}
  [[ "${logmessage}" =~ "FATAL" ]] && echo "[PID: $$] EXITING: ${logmessage}" >&2 && exit 1
  rex='^function '; [[ "${logmessage}" =~ $rex ]] && echo ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> [PID: $$] ${logmessage}${linenumber} <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<" >&2
  return 0
}

################################################################################
# Description:
#   Print API key if found in these parameters
# Requires parameters:
#   $* - Parameters passed to this script (typically either registration
#     parameters or nothing)
# Requires environment variables:
#   N/A
# Sets environment variables:
#   N/A
# Returns:
#   String API key or nothing. WARNING! Can't call anything from here that
#   prints to stdout!
function parseapikey
{
  [[ -z $TRACE ]] && logmsg ${LINENO} 'function parseapikey'
  declare -a parms=( $* )
  for (( i = 0; i < ${#parms[*]}; ++ i )); do
    if [ ${parms[$i]} = "--apikey" ]; then
      echo "${parms[$i+1]}"
      return 0
    fi
  done
}

################################################################################
# Description:
#   Install missing executable with current package manager.
# Requires parameters:
#   $1 - executable (uses "which" to check whether it is installed already)
#   $2 - yum package that installs this executable
#   $3 - apt package that installs this executable
# Requires environment variables:
#   N/A
# Sets environment variables:
#   N/A
# Returns:
#   Return code 0/1
function installmissing
{
  [[ -z $TRACE ]] && logmsg ${LINENO} 'function installmissing'
  executable=$1
  logmsg ${LINENO} "install missing ${executable}"
  [[ -z "$2" ]] && yumpackage=${executable} || yumpackage=$2
  [[ -z "$3" ]] && aptpackage=${yumpackage} || aptpackage=$3
  which ${executable} >/dev/null 2>&1 && return 0
  which yum >/dev/null 2>&1; if [ $? -eq 0 ]; then yum -y install ${yumpackage} >/dev/null 2>&1; return $?; fi
  which apt >/dev/null 2>&1; if [ $? -eq 0 ]; then apt -y install ${aptpackage} >/dev/null 2>&1; return $?; fi
  return 1
}

################################################################################
# Description:
#   Verify python exists and attempt to link it if it doesn't but python2/3 does.
# Requires parameters:
#   N/A
# Requires environment variables:
#   N/A
# Sets environment variables:
#   N/A
# Returns:
#   Return code 0/1
function linkpython
{
  [[ -z $TRACE ]] && logmsg ${LINENO} 'function linkpython'
  which python > /dev/null 2>&1 && return 0
  which python3 > /dev/null 2>&1 && ( cd /usr/bin && ln -s `which python3` python ) && return 0
  which python2 > /dev/null 2>&1 && ( cd /usr/bin && ln -s `which python2` python ) && return 0
  which python > /dev/null 2>&1 && return 0
  logmsg ${LINENO} 'WARNING! python is not on this server'
  return 1
}

################################################################################
# Description:
#   Install python if it doesn't already exist.
# Requires parameters:
#   N/A
# Requires environment variables:
#   N/A
# Sets environment variables:
#   N/A
# Returns:
#   Return code 0 or exit code 1
function installpython
{
  [[ -z $TRACE ]] && logmsg ${LINENO} 'function installpython'
  linkpython && return 0
  installmissing python || logmsg ${LINENO} "WARNING: Failed to install python"
  linkpython && return 0
  installmissing python2 || logmsg ${LINENO} "WARNING: Failed to install python2"
  linkpython && return 0
  installmissing python3 || logmsg ${LINENO} "WARNING: Failed to install python3"
  linkpython
}

################################################################################
# Description:
#   Install any missing dependencies (missing tools like curl and bzip2).
# Requires parameters:
#   N/A
# Requires environment variables:
#   N/A
# Sets environment variables:
#   N/A
# Returns:
#   Return code 0 or exit code 1
function installdependencies
{
  [[ -z $TRACE ]] && logmsg ${LINENO} 'function installdependencies'
  which yum >/dev/null 2>&1 && yum -y update >/dev/null 2>&1
  which apt >/dev/null 2>&1 && apt -y update >/dev/null 2>&1
  installpython || logmsg ${LINENO} 'FATAL ERROR! Failed to set up python on this server'
  for tool in curl bzip2 sed tar; do
    installmissing ${tool} || logmsg ${LINENO} "FATAL ERROR! Failed to set up ${tool} on this server"
  done
}

################################################################################
# Description:
#   Checks the system and sets flavor, version, package manager, system manager,
#   etc.
# Requires parameters:
#   N/A
# Requires environment variables:
#   thebasefolder - the folder that contains this script
# Sets environment variables:
#   theflavor - operating system flavor
#   theversion - operating system version
#   thesysmanager - system control manager
#   thepackagemgr - package manager
#   thearchitecture - binary architecture (v2/v3)
# Returns:
#   Return code 0 or exit code 1
function setostype
{
  [[ -z $TRACE ]] && logmsg ${LINENO} 'function setostype'
  export osflavor=unknown
  export osversion=unknown
  export theflavor=unknown
  export theversion=unknown
  export thepackagemgr=unknown
  export thesysmanager=unknown
  export thearchitecture=unknown
  export prettyname=`grep PRETTY_NAME /etc/os-release`
  [[ ! `pwd` = "${thebasefolder}" ]] && ( cd ${thebasefolder} || logmsg ${LINENO} "FATAL ERROR: Cannot access the base folder: ${thebasefolder}" )
  [[ ! -f os.data ]] && logmsg ${LINENO} "Missing OS data. Current directory: `pwd` => Running in stand-alone script mode" && return 0
  for osdata in `cat ${thebasefolder}/os.data`; do
    osflavor=`echo "${osdata}" | cut -d'+' -f1`
    [[ "${prettyname}" =~ "${osflavor}" ]] || continue
    osversion=`echo "${osdata}" | cut -d'+' -f2`
    [[ "${prettyname}" =~ "${osversion}" ]] || continue
    theflavor="${osflavor}"
    theversion="${osversion}"
    thepackagemgr=`echo "${osdata}" | cut -d'+' -f3`
    thesysmanager=`echo "${osdata}" | cut -d'+' -f4`
    thearchitecture=`echo "${osdata}" | cut -d'+' -f5`
  done
  logmsg ${LINENO} "OS type set: ${prettyname} ${theflavor} ${theversion} ${thepackagemgr} ${thesysmanager} (based on ${osflavor}: ${osversion} in ${osdata})"
}

################################################################################
# Description:
#   Run binary updater with all parameters and "forward" the return code to the
#   calling process. Used with legacy installs only. NOTE: As long as we use the
#   old parameter names, we don't have to convert "user" to "username".
# TODO: USE PYTHON TO CONVERT ' --username ' TO ' --user ' IN $*
# Requires parameters:
#   $* - Parameters passed to this script (typically either registration
#     parameters or nothing)
# Requires environment variables:
#   thebasefolder - the folder that contains this script
# Sets environment variables:
#   N/A
# Returns:
#   Exit code 0/1
function runupdater
{
  [[ -z $TRACE ]] && logmsg ${LINENO} 'function runupdater'
  cd ${thebasefolder} || logmsg ${LINENO} "FATAL ERROR: Cannot access the base folder: ${thebasefolder}"
  [[ -x cloudbackup-updater.a ]] && ( [[ -x cloudbackup-updater.setup ]] || mv -f cloudbackup-updater cloudbackup-updater.setup ) && mv -f cloudbackup-updater.a cloudbackup-updater
  ./cloudbackup-updater $* && exit 0
  logmsg ${LINENO} "FATAL ERROR: Legacy updater install failed."
}

################################################################################
# Description:
#   Get the current updater version and tar file and extract here. When this is
#   finished, the current directory is the extracted tar folder.
# Requires parameters:
#   N/A
# Requires environment variables:
#   agentrepoaddr - the address of agent repository
# Sets environment variables:
#   updaterversion - current production version of updater
#   updatertarfile - file name of updater tar
#   updatertarfolder - folder where the tar is extracted
# Returns:
#   Return code 0 or exit code 1
function getupdaterpackage
{
  [[ -z $TRACE ]] && logmsg ${LINENO} 'function getupdaterpackage'
  export updaterversion=`curl ${agentrepoaddr}/updater-version.txt`
  [[ -z "${updaterversion}" ]] && logmsg ${LINENO} "FATAL ERROR: Cannot access ${agentrepoaddr} updater version"
  export updatertarfilename=cloudbackup-updater-${updaterversion}.tar.bz2
  export updatertarfile=/tmp/${updatertarfilename}
  export updatertarfolder=/tmp/cloudbackup-updater-${updaterversion}
  curl ${agentrepoaddr}/updater/${updatertarfilename} -o ${updatertarfile}
  [[ $? -ne 0 ]] && logmsg ${LINENO} "FATAL ERROR: Failed download ${updatertarfilename} updater tar file"
  ( cd /tmp && tar jxvf ${updatertarfile} )
  [[ ! -d ${updatertarfolder} ]] && logmsg ${LINENO} "FATAL ERROR: Failed to extract ${updatertarfilename} updater tar file"
  cd ${updatertarfolder} || logmsg ${LINENO} "FATAL ERROR: Cannot access updater base folder: ${updatertarfolder}"
}

################################################################################
# Description:
#   Legacy installtion
# Requires parameters:
#   $* - Parameters passed to this script (typically either registration
#     parameters or nothing)
# Requires environment variables:
#   thebasefolder - base folder where this script was executed from.
#   updatertarfolder - (set by getupdaterpackage) folder where the tar is extracted
# Sets environment variables:
#   N/A
# Returns:
#   Exit code 0/1
function installlegacy
{
  [[ -z $TRACE ]] && logmsg ${LINENO} 'function installlegacy'
  # If cloudbackup-updater binary exists, call it with all the parameters $* and exit $?.
  [[ -x ${thebasefolder}/cloudbackup-updater ]] && ( runupdater $*; exit $? )
  # Otherwise download and run latest updater.
  getupdaterpackage
  # NOTE: In the tarball, cloudbackup-updater is a script, and the binary
  #   updater is cloudbackup-updater.a. The binary updater assumes its name is
  #   cloudbackup-updater, so it and the script must be renamed before it is
  #   run. This must be done in a single command, or the script may fail.
  logmsg ${LINENO} 'Handing off to legacy updater.'
  ( mv cloudbackup-updater cloudbackup-updater.script && mv cloudbackup-updater.a cloudbackup-updater && ./cloudbackup-updater $* & )
}

################################################################################
# Description:
#   Test whether a service exists. Used with installservice function.
# Requires parameters:
#   $1 - Name of service (without the .service extension)
# Requires environment variables:
#   installlogname - log file name
# Sets environment variables:
#   N/A
# Returns:
#   String value true or false. WARNING! Can't call anything from here that
#   prints to stdout!
function serviceexists
{
  [[ -z $TRACE ]] && logmsg ${LINENO} 'function serviceexists'
  systemctl list-units --full -all | grep -Fq "$1.service" >/dev/null 2>&1 && echo true || echo false
}

################################################################################
# Description:
#   Install a specified service. Assumes the service script for this service is
#   moved to /lib/systemd/system/. and that the matching service application is
#   in the proper place (e.g., /usr/bin or /usr/local/bin).
# Requires parameters:
#   $1 - Service installed status string: true or false
#   $2 - Name of service (with the .service extension)
# Requires environment variables:
#   installlogname - log file name
# Sets environment variables:
#   N/A
# Returns:
#   Return code 0 or exit code 1
function installservice
{
  [[ -z $TRACE ]] && logmsg ${LINENO} 'function installservice'
  export serviceinstalled=$1
  export servicename=$2
  logmsg ${LINENO} "Service install ${servicename} [${serviceinstalled}] (raw call: $*)"
  # Is the service already installed? If so, just reload and done.
  systemctl daemon-reload
  [[ ${serviceinstalled} = true ]] && return 0
  logmsg ${LINENO} "enabling service ${servicename}"
  # Install/start the service.
  systemctl enable ${servicename}.service
  logmsg ${LINENO} "restarting service ${servicename}"
  systemctl stop ${servicename}.service
  systemctl start ${servicename}.service
  # Try for up to 30 seconds to wait for the service to start up.
  logmsg ${LINENO} "wait for startup service ${servicename}"
  timeout=$( expr `date +%s` + 30 )
  while [ ! systemctl status ${servicename}.service >/dev/null 2>&1 -a $timeout -ge `date +%s`]; do sleep 2; done
  systemctl status ${servicename}.service >/dev/null 2>&1 || logmsg ${LINENO} "FATAL ERROR: Failed to start ${servicename} service"
  logmsg ${LINENO} "finished installing ${servicename}"
}

################################################################################
# Description:
#   Install a service with all flavors that don't use systemctl. WARNING! Assumes
#   that the service is already installed. This script does not support new
#   installation of the service.
# Requires parameters:
#   $1 - service name
#   $2 - local version
#   $3 - production version
# Requires environment variables:
#   theflavor - operating system flavor
#   theversion - operating system version
#   thearchitecture - binary architecture (v2/v3)
#   agentrepoaddr - agent repository URL
# Sets environment variables:
#   N/A
# Returns:
#   Return code 0/1 or exit code 1
function getinstallertarfile
{
  [[ -z $TRACE ]] && logmsg ${LINENO} 'function installoldservice'
  export myservice=$1
  export localversion=$2
  export prodversion=$3
  logmsg ${LINENO} "Local ${myservice} version ${localversion} is stale (does not match production version ${prodversion})"
  export thetarfile=${myservice}-${prodversion}.tar.bz2
  curl -O ${agentrepoaddr}/tar/${thetarfile} || logmsg ${LINENO} "FATAL ERROR: Failed download ${thetarfile} tar file"
  tar jxvf ${thetarfile}
  export thetarfolder=${myservice}-${prodversion}
  [[ ! -d ${thetarfolder} ]] && logmsg ${LINENO} "FATAL ERROR: Failed to extract ${thetarfile} tar file"
  logmsg ${LINENO} 'Finished tarfile download'
}

################################################################################
# Description:
#   Install driveclient backup agent with all flavors that don't use systemctl.
#   WARNING! Assumes that the service is already installed. This script does not
#   support new installation of the driveclient agent.
# Requires parameters:
#   N/A
# Requires environment variables:
#   theflavor - operating system flavor
#   theversion - operating system version
#   thearchitecture - binary architecture (v2/v3)
#   agentrepoaddr - agent repository URL
# Sets environment variables:
#   N/A
# Returns:
#   Return code 0/1 or exit code 1
function installoldagent
{
  [[ -z $TRACE ]] && logmsg ${LINENO} 'function installoldagent'
  logmsg ${LINENO} "Agent install mode: ${thearchitecture}: ${theflavor} ${theversion} with parameters: $*"
  # Compare local/production agent version and install-and-quit if local is stale.
  export localagentversion=unknown
  [[ -x /usr/local/bin/driveclient ]] && localagentversion=`/usr/local/bin/driveclient --version 2>/dev/null | head -1 | cut -d' ' -f2`
  export prodagentversion=`curl ${agentrepoaddr}/version.txt 2>/dev/null`
  [[ "${localagentversion}" = "${prodagentversion}" ]] && logmsg ${LINENO} "Agent already up-to-date: ${localagentversion}" && return 0
  # NOTE: After this finishes executing, ${thetarfolder} is where the files are.
  getinstallertarfile driveclient ${localagentversion} ${prodagentversion}
  # Move agent files to the proper places.
  [[ -d /etc/driveclient ]] || mkdir /etc/driveclient
  mv ${thetarfolder}/driveclient /usr/local/bin/.
  mv ${thetarfolder}/cacert.pem /etc/driveclient/.
  mv ${thetarfolder}/driveclient.service.d.script /lib/systemd/system/driveclient.service
  service driveclient restart
  logmsg ${LINENO} 'Finished agent install'
}

################################################################################
# Description:
#   Install updater for all Linux flavors that don't use systemctl. WARNING!
#   This script does not support new installation of the updater. It assumes the
#   service is already installed in some capacity.
# Requires parameters:
#   N/A
# Requires environment variables:
#   theflavor - operating system flavor
#   theversion - operating system version
#   thearchitecture - binary architecture (v2/v3)
# Sets environment variables:
#   N/A
# Returns:
#   Return code 0 or exit code 1 (via called functions)
function installoldupdater
{
  # TODO: LOOK AT installstartup.sh AND bounceservice.sh.sh FOR SERVICE INSTALL AND RESTART USING service APP.
  [[ -z $TRACE ]] && logmsg ${LINENO} 'function installoldupdater'
  logmsg ${LINENO} "Updater install mode: ${thearchitecture}: ${theflavor} ${theversion} with parameters: $*"
  # Compare local/production updater version and install-and-quit if local is stale.
  export localupdaterversion=unknown
  [[ -x /usr/bin/cloudbackup-updater ]] && localupdaterversion=`/usr/local/bin/cloudbackup-updater --version 2>/dev/null | head -1 | cut -d' ' -f2`
  export produpdaterversion=`curl ${agentrepoaddr}/version.txt 2>/dev/null`
  [[ "${localupdaterversion}" = "${produpdaterversion}" ]] && logmsg ${LINENO} "Updater already up-to-date: ${localupdaterversion}" && return 0
  # NOTE: After this finishes executing, ${thetarfolder} is where the files are.
  getinstallertarfile cloudbackup-updater ${localupdaterversion} ${produpdaterversion}
  # Move updater files to the proper places.
  cp -f ${thetarfolder}/cloudbackup-updater ${thetarfolder}/cbu-updater-service /usr/bin/.
  case ${theflavor} in
    RedHat|CentOS) cat ${thetarfolder}/cloudbackup-updater.init.redhat | sed 's/cloudbackup-updater/cbu-updater-service/g' > /etc/init.d/cbu-updater;;
    Debian|Ubuntu) cat ${thetarfolder}/cloudbackup-updater.init.debian | sed 's/cloudbackup-updater/cbu-updater-service/g' > /etc/init.d/cbu-updater;;
    Gentoo) cat ${thetarfolder}/cloudbackup-updater.init.gentoo | sed 's/cloudbackup-updater/cbu-updater-service/g' > /etc/init.d/cbu-updater;;
    *) logmsg ${LINENO} "FATAL ERROR: ${theflavor} unexpected flavor";;
  esac
  service cbu-updater restart
  logmsg ${LINENO} 'Finished updater install'
}

################################################################################
# Description:
#   Install driveclient backup agent with all flavors that use systemctl.
# Requires parameters:
#   $* - Parameters passed to this script (typically either registration
#     parameters or nothing)
# Requires environment variables:
#   theflavor - operating system flavor
#   theversion - operating system version
#   thearchitecture - binary architecture (v2/v3)
#   agentrepoaddr - agent repository URL
# Sets environment variables:
#   N/A
# Returns:
#   Return code 0/1 or exit code 1
function installagent
{
  [[ -z $TRACE ]] && logmsg ${LINENO} 'function installagent'
  logmsg ${LINENO} "Agent install mode: ${thearchitecture}: ${theflavor} ${theversion} with parameters: $*"
  # Capture agent installation status: exists = yes/no.
  export agentinstalled=`serviceexists driveclient`
  # Compare local/production agent version and install-and-quit if local is stale.
  export localagentversion=unknown
  [[ -x /usr/local/bin/driveclient ]] && localagentversion=`/usr/local/bin/driveclient --version 2>/dev/null | head -1 | cut -d' ' -f2`
  export prodagentversion=`curl ${agentrepoaddr}/version.txt 2>/dev/null`
  [[ "${localagentversion}" = "${prodagentversion}" ]] && logmsg ${LINENO} "Agent already up-to-date: ${localagentversion}" && return 0
  logmsg ${LINENO} "Local agent version ${localagentversion} is stale (does not match production version ${prodagentversion})"
  export agenttarfile=driveclient-${prodagentversion}.tar.bz2
  curl -O ${agentrepoaddr}/tar/${agenttarfile} || logmsg ${LINENO} "FATAL ERROR: Failed download ${agenttarfile} agent tar file"
  tar jxvf ${agenttarfile}
  export agenttarfolder=driveclient-${prodagentversion}
  [[ ! -d ${agenttarfolder} ]] && logmsg ${LINENO} "FATAL ERROR: Failed to extract ${agenttarfile} agent tar file"
  # Move agent files to the proper places.
  [[ -d /etc/driveclient ]] || mkdir /etc/driveclient
  mv ${agenttarfolder}/driveclient /usr/local/bin/.
  mv ${agenttarfolder}/cacert.pem /etc/driveclient/.
  mv ${agenttarfolder}/driveclient.service.d.script /lib/systemd/system/driveclient.service
  # TODO: EVENTUALLY THE AGENT MAY INSTALL THE v4driveclient BINARY
  # Register driveclient.
  python -c "import os; os.system('/usr/local/bin/driveclient $*'.replace(' --user ',' --username '))" || return 1
  # Install driveclient as a service.
  installservice ${agentinstalled} driveclient || return 1
  logmsg ${LINENO} 'Finished agent install'
}

################################################################################
# Description:
#   Install updater for all Linux flavors that use systemctl.
# Requires parameters:
#   N/A
# Requires environment variables:
#   theflavor - operating system flavor
#   theversion - operating system version
#   thearchitecture - binary architecture (v2/v3)
# Sets environment variables:
#   N/A
# Returns:
#   Return code 0 or exit code 1 (via called functions)
function installupdater
{
  [[ -z $TRACE ]] && logmsg ${LINENO} 'function installupdater'
  logmsg ${LINENO} "Updater installation: ${thearchitecture}: ${theflavor} ${theversion}"
  updateruptodate && return 0
  # Make sure we are inside a tar installer folder.
  if [[ ! -f cloudbackup-updater-py.service.d.script ]]; then
    getupdaterpackage
    logmsg ${LINENO} "Passing off to downloaded updater"
    ./cloudbackup-updater $* &
    exit 0
  fi
  # Capture updater installation status.
  export updaterinstalled=`serviceexists cbu-updater`
  export servicescript=cloudbackup-updater-py.service.d.script
  export libservicescript=/lib/systemd/system/cbu-updater.service
  # Copy/move updater files to their places.
  cp cloudbackup-updater /usr/bin/. || logmsg ${LINENO} "FATAL ERROR: copy failed cloudbackup-updater to /usr/bin/."
  mv cbu-updater-service /usr/bin/. || logmsg ${LINENO} "FATAL ERROR: move failed cbu-updater-service to /usr/bin/."
  mv ${servicescript} ${libservicescript} && [[ -f ${libservicescript} ]] || logmsg ${LINENO} "FATAL ERROR: move failed ${servicescript} to ${libservicescript}"
  installservice ${updaterinstalled} cbu-updater || return 1
  logmsg ${LINENO} 'Finished updater install'
}

################################################################################
# Description:
#   Install nscd for all Linux flavors that have the sssd bug.
# Requires parameters:
#   N/A
# Requires environment variables:
#   theflavor - operating system flavor
#   theversion - operating system version
#   thearchitecture - binary architecture (v2/v3)
# Sets environment variables:
#   N/A
# Returns:
#   Return code 0 or exit code 1
function installnscd
{
  [[ -z $TRACE ]] && logmsg ${LINENO} 'function installnscd'
  logmsg ${LINENO} "nscd installation: ${thearchitecture}: ${theflavor} ${theversion}"
  systemctl status nscd >/dev/null 2>&1 && logmsg ${LINENO} 'Package nscd found' && return 0
  which apt >/dev/null 2>&1 && apt -y update >/dev/null 2>&1
  which yum >/dev/null 2>&1 && yum -y update >/dev/null 2>&1
  which apt >/dev/null 2>&1 && apt -y install bzip2 nscd >/dev/null 2>&1
  which yum >/dev/null 2>&1 && yum -y install bzip2 nscd >/dev/null 2>&1
  systemctl enable nscd >/dev/null 2>&1
  systemctl start nscd >/dev/null 2>&1
  systemctl status nscd >/dev/null 2>&1 || logmsg ${LINENO} 'FATAL ERROR: Unable to install required package nscd'
  logmsg ${LINENO} 'Finished nscd install'
}

################################################################################
# Description:
#   Install from a tarball.
# Requires parameters:
#   $* - Parameters passed to this script (typically either registration
#     parameters or nothing)
# Requires environment variables:
#   theflavor - operating system flavor
#   theversion - operating system version
#   thearchitecture - binary architecture (v2/v3)
# Sets environment variables:
#   N/A
# Returns:
#   Return code 0/1 or exit code 0/1 (via called functions)
function tarinstall
{
  [[ -z $TRACE ]] && logmsg ${LINENO} 'function tarinstall'
  logmsg ${LINENO} "Install tar ${thearchitecture} $*"
  case ${thearchitecture} in
    v1) installoldagent; installoldupdater;;
    v2) installagent $*; installupdater;;
    v3) installnscd; installagent $*; installupdater;;
    *)  logmsg ${LINENO} "WARNING: tar install on unidentified architecture: ${thearchitecture}: ${theflavor} ${theversion}"
        installlegacy $*;;
  esac
  logmsg ${LINENO} 'Finished tar install'
}

################################################################################
# Description:
#   Check for updater/agent updates. Compare local/production updater version
#   and indicate whether local is stale.
# Requires parameters:
#   N/A
# Requires environment variables:
#   agentrepoaddr - the address of agent repository
# Sets environment variables:
#   N/A
# Returns:
#   Return code 0/1 indicating stale (0) or up-to-date (1)
function updateruptodate
{
  [[ -z $TRACE ]] && logmsg ${LINENO} 'function updateruptodate'
  export localupdaterversion=unknown
  [[ -x /usr/bin/cloudbackup-updater ]] && localupdaterversion=`/usr/bin/cloudbackup-updater --version 2>/dev/null | head -1 | cut -d' ' -f2`
  export produpdaterversion=`curl ${agentrepoaddr}/updater-version.txt 2>/dev/null`
  [[ "${localupdaterversion}" = "${produpdaterversion}" ]] && logmsg ${LINENO} "Updater already up-to-date: ${localupdaterversion}" && return 0
  logmsg ${LINENO} "Local updater version ${localupdaterversion} is stale (does not match production version ${produpdaterversion})"
  return 1
}

################################################################################
# Description:
#   Check for updater/agent updates and install-and-quit if local is stale.
# Requires parameters:
#   $* - Parameters passed to this script (typically either registration
#     parameters or nothing)
# Requires environment variables:
#   theflavor - operating system flavor
#   theversion - operating system version
#   thearchitecture - binary architecture (v2/v3)
# Sets environment variables:
#   N/A
# Returns:
#   Return code 0/1 or exit code 0/1 (depending on called functions)
function normalupdate
{
  [[ -z $TRACE ]] && logmsg ${LINENO} 'function normalupdate'
  logmsg ${LINENO} "Normal update installation: ${thearchitecture}: ${theflavor} ${theversion}"
  installagent $* || return 1
  installupdater || return 1
  logmsg ${LINENO} 'Finished normal update'
}

################################################################################
# Description:
#   Installs updater on virgin server. If registration parameters are passed,
#   register the agent. When this is done, a new installation of both updater
#   and agent should be up and running. Downloads the tarballs to install each.
#   Note that the agent is installed and registered indirectly by calling the
#   setup script from the downloaded tarball.
# Requires parameters:
#   $* - Parameters passed to this script (typically either registration
#     parameters or nothing)
# Requires environment variables:
#   agentrepoaddr - the address of agent repository
#   theflavor - operating system flavor
#   theversion - operating system version
#   thearchitecture - binary architecture (v2/v3)
# Sets environment variables:
#   N/A
# Returns:
#   Exit code 0/1
function virgininstall
{
  [[ -z $TRACE ]] && logmsg ${LINENO} 'function virgininstall'
  logmsg ${LINENO} "Updater fresh installation: ${thearchitecture}: ${theflavor} ${theversion}"
  # Download the current production updater, extract it, and launch the setup script.
  export produpdaterversion=`curl ${agentrepoaddr}/updater-version.txt 2>/dev/null`
  export updatertarfile=cloudbackup-updater-${produpdaterversion}.tar.bz2
  export updatertarfolder=cloudbackup-updater-${produpdaterversion}
  curl -O ${agentrepoaddr}/updater/${updatertarfile} || logmsg ${LINENO} "FATAL ERROR: Failed download ${updatertarfile} updater tar file"
  tar jxvf ${updatertarfile} >/dev/null 2>&1
  [[ ! -d ${updatertarfolder} ]] && logmsg ${LINENO} "FATAL ERROR: Failed to extract ${updatertarfile} updater tar file to ${updatertarfolder}"
# TODO: THIS IS ONLY FOR TESTING PURPOSES -- DELETE IT!
cp cloudbackup-updater.setup ${updatertarfolder}/cloudbackup.updater
  cd ${updatertarfolder} || logmsg ${LINENO} "FATAL ERROR: Failed to enter tar folder `realpath ${updatertarfolder}`: `pwd`"
  logmsg ${LINENO} 'Single script fresh install handing off'
  ./cloudbackup-updater $* || logmsg ${LINENO} "FATAL ERROR: Failed to run ${updatertarfolder}/cloudbackup-updater"
  logmsg ${LINENO} 'Finished single script fresh install hand-off.'
}

################################################################################
# Description:
#   Shut down a running updater service. If necessary, send a kill signal.
# Requires parameters:
#   $* - updater service name(s) to stop
# Requires environment variables:
#   N/A
# Sets environment variables:
#   N/A
# Returns:
#   Return code 0
function sendkill
{
  [[ -z $TRACE ]] && logmsg ${LINENO} 'function sendkill'
  for i in $*; do
    systemctl status $i >/dev/null 2>&1 && systemctl stop $i >/dev/null 2>&1 && return 0
  done
  for i in $*; do
    # Make sure updater service is running. If not, we're done.
    ps -ef | grep $i | grep python >/dev/null 2>&1 || return 0
    # Get the pid of the updater service.
    pidnum=`ps -ef | grep $i | grep python | awk '{ print $2 }'`
    # Try to kill the updater service.
    [[ ${pidnum} -gt 0 ]] && kill ${pidnum}
    # Wait for updater service to finish.
    timeout=$( expr `date +%s` + 30 )
    while [ ! ps -ef | grep $i | grep python >/dev/null 2>&1 -a $timeout -ge `date +%s`]; do sleep 2; done
    ps -ef | grep $i | grep python >/dev/null 2>&1 || return 0
    # If it still won't go away, do a sure-kill.
    pidnum=`ps -ef | grep $i | grep python | awk '{ print $2 }'`
    [[ ${pidnum} -gt 0 ]] && kill -9 ${pidnum}
  done
}

################################################################################
# Description:
#   Display help for this script.
# Requires parameters:
#   N/A
# Requires environment variables:
#   N/A
# Sets environment variables:
#   N/A
# Returns:
#   Return code 0
function displayhelp
{
  [[ -z $TRACE ]] && logmsg ${LINENO} 'function displayhelp'
  echo 'syntax:
  --help
  --version
  --shutdown
  --configure --username <username> --apikey <apiKey> --flavor <flavor> --datacenter <dataCenter> --apihost <apiDrivesrvr> --force
  ' >&2
}

################################################################################
# Description:
#   Print version information to stdout.
# Requires parameters:
#   N/A
# Requires environment variables:
#   N/A
# Sets environment variables:
#   N/A
# Returns:
#   String with version information. WARNING! Can't call anything from here
#   that prints to stdout!
function printversioninfo
{
  [[ -z $TRACE ]] && logmsg ${LINENO} 'function printversioninfo'
  echo 'Version: 3.0.010751 (4a54c6cd8c17f22232c0e66d367f7d807e3fcf74)'
  echo 'Build Date: Nov 30 2021 23:39:19 (Release)'
}

################################################################################
# Description:
#   Parse script args. Fail on bad argv[1]. Immediately execute --help,
#   --shutdown, and --version commands and exit. Return to caller for empty or
#   --configure args.
# Requires parameters:
#   $1 - first argument passed to the script
# Requires environment variables:
#   N/A
# Sets environment variables:
#   N/A
# Returns:
#   Return code 0 or exit code 0/1
function parseparams
{
  [[ -z $TRACE ]] && logmsg ${LINENO} 'function parseparams'
  logmsg ${LINENO} "parameters passed to this script: $*"
  if [ -z "$1" ]; then return 0; fi
  case $1 in
    --help|-h) displayhelp; exit 0;;
    --version|-v) printversioninfo; exit 0;;
    --configure|-c) return 0;;
    --shutdown|-s) sendkill cloudbackup-updater cbu-updater-service; exit 0;;
    *) logmsg ${LINENO} "FATAL ERROR: Unexpected parameter: $1";;
  esac
}

################################################################################
################################################################################
##                                 MAIN CODE                                  ##
################################################################################
################################################################################

export installlogname=/var/log/cbu-installer.log
export agentrepoaddr=http://agentrepo.drivesrvr.com
export thescriptname=`basename $0`
export thebasefolder="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
export theapikey=`parseapikey $*`
export installmode=`[[ -f ${thebasefolder}/cloudbackup-updater-py.service.d.script ]] && echo tar || ( [[ "${thebasefolder}" = "/usr/bin" ]] && echo existing || echo virgin )`
export PATH=${PATH}:${thebasefolder}
logmsg ${LINENO} "DEVELOPMENT `for i in $(printversioninfo); do echo -n $i' '; done`"
logmsg ${LINENO} "Updater setup executed from this folder: ${thebasefolder} (current `pwd`) on this host `hostname` with these parameters: $*."
logmsg ${LINENO} "Initial values: agent repo: ${agentrepoaddr} installer log: ${installlogname} script: ${thescriptname} install mode: ${installmode}"
parseparams $*
installdependencies
setostype
cd ${thebasefolder}
case ${installmode} in
  tar) tarinstall $*;;
  virgin) virgininstall $*;;
  existing) normalupdate $*;;
  *) logmsg ${LINENO} 'FATAL ERROR: unknown install mode';;
esac
logmsg ${LINENO} 'Finished'

