#!/bin/sh

# Usage:
#   curl ... | ENV_VAR=... sh -
#       or
#   ENV_VAR=... ./install.sh
#
# Example:
#   Installing an agent to point at a server:
#     curl ... | AIWARE_TOKEN=xxx AIWARE_CONTROLLER=https://server-url:9000 sh -
#
# Setting version:
#   Looks for the first of:
#   - AIWARE_GIT_VERSION
#     40-char git commit hash. Causes INSTALL_RELEASES_URL to be ignored. Only works for edge
#     services and may cause core or app services not to load.
#
#   - INSTALL_AIWARE_VERSION
#     Quad-dotted aiWARE version number like "3.34.2.4101". Only works for edge services and
#     may cause core or app services not to load.
#
#   - AIWARE_MANIFEST_VERSION
#   - AIWARE_MANIFEST_TIER
#     Use the version information in a manifest found at
#     https://get.aiware.com/aiware-versions/${AIWARE_MANIFEST_TIER}/aiware-${AIWARE_MANIFEST_VERSION}.yaml
#     This file is a Helm chart containing all the service component versions as annotations. 
#     Installer fails if this file is not found or does not contain an edge-agent version.
#
#   - INSTALL_AIWARE_CHANNEL
#     Name of channel to read version from, like "azure-stage". Must match a "latest-agent-"
#     file in the https://get.aiware.com/dist/ directory. Installer fails if file not found.
#     Only affects certain edge services and other services will be loaded from the "prod"
#     channel
# 
#   - default
#     If none of these are defined, uses the manifest version found in
#     https://get.aiware.com/aiware-versions/${AIWARE_MANIFEST_TIER}/aiware-latest.txt
#
# Environment variables:
#   - AIWARE_*
#     Environment variables which begin with AIWARE_ will be preserved for the
#     systemd service to use. Setting AIWARE_URL without explicitly setting
#     a systemd exec command will default the command to "agent", and we
#     enforce that AIWARE_TOKEN  is also set.
#
#   - INSTALL_AIWARE_SKIP_SERVICE
#     Only install AIWARE command line and skip installing the service.
#
#   - INSTALL_AIWARE_SKIP_START
#     Install the service, but do not start it
#
#   - INSTALL_AIWARE_BIN_DIR
#     Directory to install aiware-agent binary, links, and uninstall script to, or use
#     /usr/local/bin as the default
#
#   - INSTALL_AIWARE_SYSTEMD_DIR
#     Directory to install systemd service and environment files to, or use
#     /etc/systemd/system as the default
#
#   - INSTALL_AIWARE_EXEC or script arguments
#     Command with flags to use for launching aiware in the systemd service, if
#     the command is not specified will default to "agent" if AIWARE_URL is set
#     or "server" if not. The final systemd command resolves to a combination
#     of EXEC and script args ($@).
#
#     The following commands result in the same behavior:
#       curl ... | INSTALL_AIWARE_EXEC="--disable-agent" sh -s -
#       curl ... | sh -s - --controller http://10.1.10.1:9000/
#
#   - INSTALL_AIWARE_NAME
#     Name of systemd service to create, will default from the aiware-agent exec command
#     if not specified. If specified the name will be prefixed with 'aiware-'.
#
# K8S Installation:
#   Documentation
#   - Full docs for k3s agent https://rancher.com/docs/k3s/latest/en/installation/install-options/agent-config/
#   - Full docs for k3s server https://rancher.com/docs/k3s/latest/en/installation/install-options/server-config/
#
#   Environment Variables
#   - INSTALL_AIWARE_K8S
#     Must be true to install kubernetes
#
#   - INSTALL_AIWARE_K8S_MODE
#     aws is the only supported value. in the aws environment this env var expects two things.
#     1. it has permission to join an eks cluster
#     2. the eks cluster exists
#     This must only be used on a particular ami ami-0f1dee4fc4df48989 as of 10/2/21
#     Example INSTALL_AIWARE_K8S_MODEINSTALL_AIWARE_K8S_MODE=aws
#
#   - K3S_URL
#     If you'd like to cluster the k8s nodes, you can set this variable. Must not be set for box #1
#     must be set to cluster box #2-N. example: export K3S_URL=https://172.31.67.192:6443
#
#   - K3S_TOKEN
#     This is a k3s environment variable. you must set this value to add one k3s node to another.
#     You get this value by running
#
#   - INSTALL_AIWARE_KNATIVE
#     During a kuberntes installation, this setting installs knative if set
#
#   - INSTALL_AIWARE_KUBECONFIG
#     If you have an existing kubernetes cluster, you can do `export INSTALL_AIWARE_KUBECONFIG=$(cat ~/.kube/config | base64 -w 0)`
#     to export the kube config to a single line base64. this will then install kubectl and the config to access the cluster
#

DEFAULT_RELEASES_URL=https://get.aiware.com/dist
RELEASES_URL=$DEFAULT_RELEASES_URL
RELEASES_URL_PREFIX=dist
DOWNLOADER=
LOGFILE=/var/log/aiware-install.log

# --- helper functions for logs ---
info() {
  echo "INFO  [$(date "+%F %T")] " "$@" 1>&2
}
warn() {
  echo "WARN  [$(date "+%F %T")] " "$@" 1>&2
}
fatal() {
  echo "ERROR [$(date "+%F %T")] " "$@" 1>&2
  exit 1
}

intro() {
  info ""
  info ""
  info "Installing aiWARE"
  info "Please see https://docs.veritone.com/#/deployment/reference/envs for more information on the installation environment variables"
}

# --- fatal if there is an unknown mode requested ---
verify_run_modes() {
  [ "${INSTALL_AIWARE_SKIP_SERVICE}" = true ] && return

  local VALID_MODES=',none,cli,prometheus,api,service,apps,eventing,controller,db,engine,registry,docker,nfs,automatestudio,automate,lb,nsq,es,redis,ceph-fs,cephfs,minio,storage,graphdb,icecast,rtsp,influxdb,logging,hubagent,kube,config-controller,'
  local MODE_USAGE='
      Valid values for AIWARE_MODE are:
        "None" or "CLI" to install the aiWARE CLI but not the aiWARE service.
      Other modes include:
        Controller, Engine, Registry, NFS, LB, NSQ
        CephFS, DB, ES, MinIO, Redis, Storage, GraphDB
        API, Eventing
        Apps, Automate
        HubAgent
        kube, Config-Controller
      Separate multiple modes with commas. Modes are case insensitive.'

  if [ "${AIWARE_MODE}" = "" ]; then
    fatal "AIWARE_MODE is not set.
${MODE_USAGE}"
  fi

  local MODE
  for MODE in $(echo "${AIWARE_MODE}" | tr -C 'A-Za-z-' ' '); do
    if echo "${VALID_MODES}" | grep -qi ",${MODE},"; then continue; fi
    fatal "AIWARE_MODE contains an unknown mode '${MODE}'
${MODE_USAGE}"
  done

  return
}

# --- determines if a specific mode is present for this host ---
has_mode() {
  local mode="$1"
  echo ",$AIWARE_MODE," | tr -d '[:space:]' | tr '[:upper:]' '[:lower:]' | grep -q ",$mode,"
}

check_installer() {
  if [ "${INSTALL_RELEASES_URL}" != "" ]; then
    RELEASES_URL=${INSTALL_RELEASES_URL}
  fi

  if [ "${INSTALL_RELEASES_URL_PREFIX}" != "" ]; then
    RELEASES_URL_PREFIX=${INSTALL_RELEASES_URL_PREFIX}
  fi

  # skip service launch if mode is for CLI only
  ( has_mode cli || has_mode none ) && INSTALL_AIWARE_SKIP_SERVICE=true

  info "Using ${RELEASES_URL} with prefix ${RELEASES_URL_PREFIX}"
}

is_RHEL7() {
  if command -v hostnamectl >/dev/null 2>&1; then
    local cpe_os=$(hostnamectl | grep "cpe:/")
    [ -n "$cpe_os" ] && [[ "$cpe_os" =~ "cpe:/o:redhat:enterprise_linux:7" ]] && return 0 || return 1
  else
    return 1
  fi
}

# --- fatal if no systemd or openrc ---
HAS_APT=false
HAS_DNF=false
HAS_YUM=false
HAS_SYSTEMD=false
HAS_OPENRC=false
HAS_MACOSX=false
verify_system() {
  # determine which package installer to use. Macos is handled separately
  if command -v apt-get >/dev/null 2>&1; then
    # debian / ubuntu
    HAS_APT=true
  elif command -v dnf >/dev/null 2>&1; then
    # redhat 8+ / fedora
    HAS_DNF=true
  elif command -v yum >/dev/null 2>&1; then
    # redhat7 / fedora
    HAS_YUM=true
  fi

  if [ -x /sbin/openrc-run ]; then
    HAS_OPENRC=true
    return
  fi

  if [ -d /run/systemd ]; then
    if [ $(which systemctl) != "" ]; then
      HAS_SYSTEMD=true
      return
    fi
  fi

  if [ -d /Library/LaunchDaemons ]; then
    HAS_MACOSX=true
    return
  fi

  fatal 'Can not find systemd, openrc or mac os x to use as a process supervisor for aiware'
}

# install the ifconfig tool
install_ifconfig() {
  command -v ifconfig >/dev/null 2>&1 && return

  info 'Installing ifconfig'
  if $HAS_APT; then
    apt-get install net-tools
  elif $HAS_DNF; then
    dnf install net-tools
  elif $HAS_YUM; then
    yum install net-tools
  else
    fatal 'Please install "ifconfig" and try again.'
  fi

  command -v ifconfig >/dev/null 2>&1 || {
    fatal 'Failed to install "ifconfig". Please install "ifconfig" from the "net-tools" package and try again.'
  }
}


run_shasum() {
  if $HAS_MACOSX; then
    shasum -a 256 "$@"
  else
    # Assume Linux
    sha256sum "$@"
  fi
}

# --- fatal if no systemd or openrc ---
verify_system_docker() {
  [ "${INSTALL_AIWARE_SKIP_SERVICE}" = true ] && return

  DOCT=$(which docker)
  if [ "${DOCT}" = "" ]; then
    fatal 'Unable to find docker command.  Please install docker before continuing'
    return
  fi

  HAS_DOCKER=true
  return

}

# --- add quotes to command arguments ---
quote() {
  for arg in "$@"; do
    printf '%s\n' "$arg" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/'/"
  done
}

# --- add indentation and trailing slash to quoted args ---
quote_indent() {
  printf ' \\\n'
  for arg in "$@"; do
    printf '\t%s \\\n' "$(quote "$arg")"
  done
}

# --- escape most punctuation characters, except quotes, forward slash, and space ---
escape() {
  printf '%s' "$@" | sed -e 's/\([][!#$%&()*;<=>?\_`{|}]\)/\\\1/g;'
}

# --- escape double quotes ---
escape_dq() {
  printf '%s' "$@" | sed -e 's/"/\\"/g'
}

# --- define needed environment variables ---
setup_env() {
  # --- use command args if passed or create default ---
  case "$1" in
  -* | "")
    info 'aiware-agent command is not provided. Using agent'
    CMD_AIWARE=agent
    ;;
    # --- command is provided ---
  *)
    CMD_AIWARE=$1
    shift

    info "aiware-agent command is not provided. Using ${CMD_AIWARE}"
    ;;
  esac
  CMD_AIWARE_EXEC="${CMD_AIWARE}$(quote_indent "$@")"

  # --- use systemd name if defined or create default ---
  if [ -n "${INSTALL_AIWARE_NAME}" ]; then
    SYSTEM_NAME=aiware-${INSTALL_AIWARE_NAME}
  else
    if [ "${CMD_AIWARE}" = server ]; then
      SYSTEM_NAME=aiware
    else
      SYSTEM_NAME=aiware-${CMD_AIWARE}
    fi
  fi

  # --- check for invalid characters in system name ---
  valid_chars=$(printf '%s' "${SYSTEM_NAME}" | sed -e 's/[][!#$%&()*;<=>?\_`{|}/[:space:]]/^/g;')
  if [ "${SYSTEM_NAME}" != "${valid_chars}" ]; then
    invalid_chars=$(printf '%s' "${valid_chars}" | sed -e 's/[^^]/ /g')
    fatal "Invalid characters for system name:
            ${SYSTEM_NAME}
            ${invalid_chars}"
  fi

  # --- set related files from system name ---
  SERVICE_AIWARE=${SYSTEM_NAME}.service
  UNINSTALL_AIWARE_SH=${SYSTEM_NAME}-uninstall.sh
  UPGRADE_AIWARE_SH=${SYSTEM_NAME}-upgrade.sh
  USAGE_AIWARE_SH=aiware-usage.sh
  KILLALL_AIWARE_SH=aiware-agent-killall.sh

  # --- set cache to default ---
  if [ "${AIWARE_CACHE}" = "" ]; then
    AIWARE_CACHE=/cache
  fi

  # --- use sudo if we are not already root ---
  SUDO=sudo
  if [ $(id -u) -eq 0 ]; then
    SUDO=
  fi

  # --- use systemd type if defined or create default ---
  if [ -n "${INSTALL_AIWARE_TYPE}" ]; then
    SYSTEMD_TYPE=${INSTALL_AIWARE_TYPE}
  else
    if [ "${CMD_AIWARE}" = server ]; then
      SYSTEMD_TYPE=notify
    else
      SYSTEMD_TYPE=exec
    fi
  fi

  # --- use binary install directory if defined or create default ---
  if [ -n "${INSTALL_AIWARE_BIN_DIR}" ]; then
    BIN_DIR=${INSTALL_AIWARE_BIN_DIR}
  elif is_RHEL7; then
    info "Detected RedHat version 7.x and INSTALL_AIWARE_BIN_DIR is not defined. Using /usr/bin"
    BIN_DIR=/usr/bin
  else
    BIN_DIR=/usr/local/bin
  fi

  # --- use systemd directory if defined or create default ---
  if [ -n "${INSTALL_AIWARE_SYSTEMD_DIR}" ]; then
    SYSTEMD_DIR="${INSTALL_AIWARE_SYSTEMD_DIR}"
  else
    SYSTEMD_DIR=/etc/systemd/system
  fi

  # --- use service or environment location depending on systemd/openrc ---
  if $HAS_SYSTEMD; then
    FILE_AIWARE_SERVICE=${SYSTEMD_DIR}/${SERVICE_AIWARE}
    FILE_AIWARE_ENV=${SYSTEMD_DIR}/${SERVICE_AIWARE}.env
  elif $HAS_OPENRC; then
    $SUDO mkdir -p /etc/aiware
    FILE_AIWARE_SERVICE=/etc/init.d/${SYSTEM_NAME}
    FILE_AIWARE_ENV=/etc/aiware/${SYSTEM_NAME}.env
  elif $HAS_MACOSX; then
    info 'Setting up env for macosx'
    $SUDO mkdir -p /usr/local/aiware /usr/local/etc/init.d/ /opt/aiware || true
    FILE_AIWARE_SERVICE=/Library/LaunchDaemons/${SYSTEM_NAME}.plist
    FILE_AIWARE_ENV=/opt/aiware/${SYSTEM_NAME}.env
    FILE_AIWARE_SCRIPT=/opt/aiware/start.sh

    if [ "${AIWARE_CACHE}" = "/cache" ]; then
      AIWARE_CACHE=/opt/aiware/cache
    fi
  fi

  # --- get hash of config & exec for currently installed aiware ---
  PRE_INSTALL_HASHES=$(get_installed_hashes)

  info "Setting aiware cache to ${AIWARE_CACHE}"
  export AIWARE_CACHE=${AIWARE_CACHE}
}


# --- verify an executabe aiware binary is installed ---
verify_aiware_is_executable() {
  if [ ! -x ${BIN_DIR}/aiware-agent ]; then
    fatal "Executable aiware binary not found at ${BIN_DIR}/aiware-agent"
  fi
}

# --- set arch and suffix, fatal if architecture not supported ---
setup_verify_arch() {
  if [ -z "$ARCH" ]; then
    ARCH=$(uname -m)
  fi

  ARCH_OS=$(uname | tr '[:upper:]' '[:lower:]')
  case $ARCH in
  amd64)
    ARCH=amd64
    SUFFIX=
    ;;
  x86_64)
    ARCH=amd64
    SUFFIX=
    ;;
  arm64)
    ARCH=arm64
    SUFFIX=-${ARCH}
    ;;
  aarch64)
    ARCH=arm64
    SUFFIX=-${ARCH}
    ;;
  arm*)
    ARCH=arm
    SUFFIX=-${ARCH}hf
    ;;
  *)
    fatal "Unsupported architecture $ARCH"
    ;;
  esac

  ARCH="$ARCH_OS-$ARCH"
  info "  Architecture: $ARCH"
}

# --- verify existence of network downloader executable ---
verify_downloader() {
  # Return failure if it doesn't exist or is no executable
  [ -x "$(which $1)" ] || return 1

  # Set verified executable as our downloader program and return success
  DOWNLOADER=$1
  return 0
}

# --- create temp directory and cleanup when done ---
setup_tmp() {
  TMP_DIR=$(mktemp -d -t aiware-install.XXXXXXXXXX)
  rm -rf ${TMP_DIR}
  mkdir -p ${TMP_DIR}
  cleanup() {
    code=$?
    set +e
    trap - EXIT
    rm -rf ${TMP_DIR}
    exit $code
  }
  trap cleanup INT EXIT
}

# --- use desired aiware version if defined or find latest ---
# Sets VERSION_AIWARE to the desired version to use. This could be either a version number
# (3.28.3.4011) or a git commit hash (78e4d3ecee97eafac0e14bb726563e82b8c1e9b3). If this detects
# a git commit hash, then it will reset the RELEASES_URL to the "builds" directory, ignoring any
# custom value provided by the user in INSTALL_RELEASES_URL
get_release_version() {
  if [ -n "${AIWARE_GIT_VERSION}" ]; then
    info "  AIWARE_GIT_VERSION = $AIWARE_GIT_VERSION"
    VERSION_AIWARE=${AIWARE_GIT_VERSION}
  elif [ -n "${INSTALL_AIWARE_VERSION}" ]; then
    info "  INSTALL_AIWARE_VERSION = $INSTALL_AIWARE_VERSION"
    VERSION_AIWARE=${INSTALL_AIWARE_VERSION}
  elif [ -n "${AIWARE_MANIFEST_VERSION}" ]; then
    get_release_version_manifest "$AIWARE_MANIFEST_VERSION"
  elif [ -n "${INSTALL_AIWARE_CHANNEL}" ]; then
    get_release_version_channel "$INSTALL_AIWARE_CHANNEL"
  else
    get_release_version_from_controller || get_default_release_version
    :
  fi

  # normalize version
  VERSION_AIWARE=$(echo "$VERSION_AIWARE" | tr -d ' \t\n\r')

  # check if version is a git hash
  if echo "$VERSION_AIWARE" | grep -iEq '^[a-z0-9]{40}$'; then
    # git commit release are in a different location than semver releases
    RELEASES_URL=${DEFAULT_RELEASES_URL}/builds/agent
    RELEASES_URL_PREFIX="dist/builds"
    info "  Changing releases URL to ${RELEASES_URL}"
  fi

  if [ "$INSTALL_AIWARE_SKIP_SERVICE" = true ]; then
    info "  Installing CLI version ${VERSION_AIWARE}"
  else
    info "  Installing aiWARE agent version ${VERSION_AIWARE}"
  fi
}

# Init manifest file environment
[ "$AIWARE_MANIFEST_TIER" ] || export AIWARE_MANIFEST_TIER=stable
MANIFEST_URL="https://get.aiware.com/aiware-versions/${AIWARE_MANIFEST_TIER}"

# given a manifest version, fetches the manifest if necessary and validates it
get_release_version_manifest() {
  manifestVersion=${1#manifest:}

  # verify we have the right manifest. if not, go get it and validate it
  manifestFileUrl="${MANIFEST_URL}/aiware-${manifestVersion}.yaml"
  manifestLocalFilePath="/opt/aiware/aiware-${manifestVersion}.yaml"
  info "  AIWARE_MANIFEST_VERSION: $manifestVersion"

  if [ -s "${manifestLocalFilePath}" ]; then
    # version manifests are immutable
    info "  Version manifest ${manifestLocalFilePath} already exists, skipping download"
  else
    info "    Downloading ${manifestFileUrl}"
    download_optional "$manifestLocalFilePath" "$manifestFileUrl"
  fi

  # check for successful download or backup file
  [ -f "$manifestLocalFilePath" ] && {
    grep -q Error "$manifestLocalFilePath" && rm -f "$manifestLocalFilePath"
  }
  if [ -s "${manifestLocalFilePath}" ]; then
    rm -f /opt/aiware/aiware-version.yaml
    ln -s "${manifestLocalFilePath}" /opt/aiware/aiware-version.yaml
  else
    warn "    Unable to download version manifest ${manifestFileUrl}"
    [ "$AIWARE_MANIFEST_TIER" = dev ] || {
      # check to see if this is a tier configuration problem
      devManifest=$(fetch "$(dirname "$MANIFEST_URL")/dev/aiware-${manifestVersion}.yaml")
      [ "$devManifest" ] && {
        fatal "Version $manifestVersion exists, but has not been promoted to the aiWARE '$AIWARE_MANIFEST_TIER' environment"
      }
    }
    if [ -f "/opt/aiware/aiware-version.yaml" ]; then
      info "  Using existing /opt/aiware/aiware-version.yaml instead"
    else
      fatal "  No backup /opt/aiware/aiware-version.yaml found. Please correct the version configuration and try again"
    fi
  fi

  # extract the edge-agent version from the manifest
  VERSION_AIWARE=$(grep "edge-agent" /opt/aiware/aiware-version.yaml | cut -d: -f2 | head -n1 | tr -d ' \t\n')
  [ "$VERSION_AIWARE" ] || fatal "Manifest file ${manifestFileUrl} does not contain a version for 'edge-agent'"
}

# given a channel name, fetches the channel file and reads the version from it
get_release_version_channel() {
  channelName=${1#channel:}
  channelUrl="${RELEASES_URL}/latest-agent-${channelName}"

  info "  INSTALL_AIWARE_CHANNEL: from $channelUrl"

  # get the version from the channel file
  VERSION_AIWARE=$(fetch "$channelUrl")
  [ "$VERSION_AIWARE" ] || fatal "  Unable to read channel file $channelUrl"
  if echo "$VERSION_AIWARE" | grep -iq 'error'; then
    fatal "  Unable to load version from file $channelUrl: ${VERSION_AIWARE}"
  fi
}

# contact the controller of the cluster we're in and get which version we're supposed to be
# running. Returns 0 (success) if we set up a version and 1 (failure) if we couldn't find a
# controller or get a valid version
get_release_version_from_controller() {
  [ "$AIWARE_CONTROLLER" ] || return 1

  # read the intended version from the current controller
  controllerInfo=$(fetch "$AIWARE_CONTROLLER/ping") || return $?
  [ "$controllerInfo" ] || { info "  (Controller data not found)"; return 1; }
  echo "$controllerInfo" | grep -q 'intendedVersion' || {
    info "  (Controller ${AIWARE_CONTROLLER} has no intended version)"
    return 1
  }
  intendedVersion=$(echo "$controllerInfo" | tr '\n' ' ' | sed 's/^.*intendedVersion"[: ]*"//;s/".*$//')

  info "  Intended version from ${AIWARE_CONTROLLER}: $intendedVersion"

  case "$intendedVersion" in 
    manifest:*) 
      export AIWARE_MANIFEST_VERSION="${intendedVersion#manifest:}"
      get_release_version_manifest "$intendedVersion"
      ;;
    channel:*) 
      export INSTALL_AIWARE_CHANNEL="${intendedVersion#channel:}"
      get_release_version_channel "$intendedVersion"
      ;;
    *) 
      fatal "Controller is running an unexpected version: $intendedVersion"
      ;;
  esac
  return 0
}

# configures the default release version to use assuming there are no explicit versions defined
get_default_release_version() {
  # fetch the latest version this tier is permitted to run
  latestManifestUrl="${MANIFEST_URL}/aiware-latest.txt"
  info "  Retrieving default version from ${latestManifestUrl}"
  latestManifestVersion=$(fetch "$latestManifestUrl") || {
    fatal "    Unable to read the latest manifest version: $?"
  }
  [ "$latestManifestVersion" ] || {
    fatal "    Latest manifest version file is empty"
  }
  if echo "$latestManifestVersion" | grep -iq 'error'; then
    fatal "    Unable to read manifest version from $latestManifestUrl: $latestManifestVersion"
  fi

  export AIWARE_MANIFEST_VERSION="$latestManifestVersion"
  get_release_version_manifest "$latestManifestVersion"
}

# Download contents of required URL to file. Fatal exit if file does not exist. May be used for
# large files, so has no timeout
download() {
  targetFile=$1
  sourceURL=$2

  case $DOWNLOADER in
  curl)  curl -o $targetFile -sfL $sourceURL || fatal "  Unable to download $sourceURL" ;;
  wget)  wget -qO $targetFile $sourceURL     || fatal "  Unable to download $sourceURL" ;;
  *)     fatal "  Unknown download executable '$DOWNLOADER'" ;;
  esac
}

# Download contents of optional URL to file. Returns error code if file does not exist.
# 15-second timeout (only for small files)
download_optional() {
  targetFile=$1
  sourceURL=$2

  mkdir -p "$(dirname "$targetFile")"

  case $DOWNLOADER in
  curl)  curl --connect-timeout 15 --max-time 60 -o $targetFile -sfL $sourceURL; return $? ;;
  wget)  wget --timeout=15 -qO $targetFile $sourceURL;     return $? ;;
  *)     fatal "  Unknown download executable '$DOWNLOADER'" ;;
  esac
}

# Output the contents of a URL for a text file. If contents contains "error" then no results are
# output and return value is 120. 15-second timeout (only for small files)
fetch() {
  sourceURL=$1  

  targetFile=$TMP_DIR/fetch.txt
  resultCode=0

  case $DOWNLOADER in
  curl)  curl --connect-timeout 15 --max-time 60 -o $targetFile -sfL $sourceURL; resultCode=$? ;;
  wget)  wget --timeout=15 -qO $targetFile $sourceURL    ; resultCode=$? ;;
  *)     fatal "  Incorrect executable '$DOWNLOADER'" ;;
  esac

  # handle error conditions
  [ $resultCode -gt 0 ] && return $resultCode
  [ -s "$targetFile" ] || return 0 # success, but no content
  grep -iq 'error' "$targetFile" && return 120
  cat "$targetFile"
}

# Download the sha256 checksum file and output the hash value
download_hash() {
  serviceName=$1
  hashURL=$2
  info "  Downloading $serviceName hash $hashURL"
  download "$TMP_DIR/$serviceName.hash" $hashURL
  hashInfo=$(grep "${serviceName}.*${VERSION_AIWARE}.*${ARCH}" "$TMP_DIR/$serviceName.hash")
  [ "$hashInfo" ] || fatal "  Hash file $hashURL does not conatain hash for the correct version of $serviceName"
  rm -f "$TMP_DIR/$serviceName.hash"
  echo ${hashInfo%% *}
}

# --- check hash against installed version ---
installed_hash_matches() {
  serviceName=$1
  expectedHash=$2

  [ -x ${BIN_DIR}/$serviceName ] || return 1

  actualHash=$(run_shasum ${BIN_DIR}/$serviceName)
  actualHash=${actualHash%% *}
  [ "$actualHash" = "$expectedHash" ]
}

# Download the service from the URL and output the downloaded path
download_binary() {
  serviceName=$1
  sourceServiceURL=$2
  info "  Downloading $serviceName binary $sourceServiceURL"
  download "$TMP_DIR/$serviceName" "$sourceServiceURL"
  echo "$TMP_DIR/$serviceName"
}

# --- verify downloaded binary hash ---
verify_binary() {
  serviceName=$1
  servicePath=$2
  expectedHash=$3
  info "  Verifying $serviceName download"
  actualHash=$(run_shasum "$servicePath")
  actualHash=${actualHash%% *}
  [ "$actualHash" != "$expectedHash" ] && fatal "Downloaded $serviceName checksum ($actualHash) does not match $expectedHash"
}

# Move binary from the current service path to the final binary directory
setup_binary() {
  serviceName=$1
  installName=$2
  servicePath=$3
  info "  Installing $serviceName to $BIN_DIR/$installName"
  chmod 755 "$servicePath"
  if $HAS_MACOSX; then
    $SUDO chown root:wheel "$servicePath"
  else
    $SUDO chown root:root "$servicePath"
  fi

  $SUDO mv -f "$servicePath" "$BIN_DIR/$installName"

  if command -v getenforce >/dev/null 2>&1; then
    if [ "Disabled" != $(getenforce) ]; then
      if command -v semanage >/dev/null 2>&1; then
        info 'SELinux is enabled, setting permissions'
        if ! $SUDO semanage fcontext -l | grep "$BIN_DIR/$installName" >/dev/null 2>&1; then
          $SUDO semanage fcontext -a -t bin_t "$BIN_DIR/$installName"
        fi
        $SUDO restorecon -v "$BIN_DIR/$installName" >/dev/null
      else
        fatal 'SELinux is enabled but semanage is not found'
      fi
    fi
  fi
}

# --- download and verify aiware agent ---
download_and_verify() {
  if [ "$INSTALL_AIWARE_SKIP_DOWNLOAD" = true ]; then
    info 'Skipping aiware-agent download and verify'

    if [ "${INSTALL_AIWARE_USE_LOCAL_AGENT}" = true ]; then
      info 'Copying local edge-cli to $BIN_DIR/ai location'
      cp ./edge-cli ${BIN_DIR}/ai

      [ "$INSTALL_AIWARE_SKIP_SERVICE" = true ] || {
        info 'Copying local ./edge-agent to ${BIN_DIR}/aiware-agent location'
        cp ./edge-agent ${BIN_DIR}/aiware-agent
      }
    fi
    verify_aiware_is_executable
    return
  fi

  info "Determining version to install:"
  setup_verify_arch
  verify_downloader curl || verify_downloader wget || fatal 'Can not find curl or wget for downloading files'
  setup_tmp
  get_release_version

  # install aiware-cli (ai)
  {
    info "Installing aiware-cli (as ai)"
    cliReleasesURL=$(echo "$RELEASES_URL" | sed 's|/agent$|/aiware-cli|')
    expectedCliHash=$(download_hash aiware-cli "$cliReleasesURL/aiware-cli-${VERSION_AIWARE}-sha256sum-${ARCH}.txt") || exit 1
    if installed_hash_matches ai "$expectedCliHash"; then
      info "  Skipping download of aiware-cli, installed hash matches"
    else
      cliBinaryPath=$(download_binary aiware-cli "$cliReleasesURL/aiware-cli-${VERSION_AIWARE}-${ARCH}") || exit 1
      verify_binary aiware-cli "$cliBinaryPath" "$expectedCliHash"
      setup_binary aiware-cli ai "$cliBinaryPath"
    fi
  }

  # install aiware-agent
  [ "$INSTALL_AIWARE_SKIP_SERVICE" = true ] || {
    info "Installing aiware-agent"
    expectedAgentHash=$(download_hash aiware-agent "${RELEASES_URL}/aiware-agent-${VERSION_AIWARE}-sha256sum-${ARCH}.txt") || exit 1
    if installed_hash_matches aiware-agent "$expectedAgentHash"; then
      info "  Skipping download of aiware-agent, installed hash matches"
    else
      agentBinaryPath=$(download_binary aiware-agent "${RELEASES_URL}/aiware-agent-${VERSION_AIWARE}-${ARCH}") || exit 1
      verify_binary aiware-agent "$agentBinaryPath" "$expectedAgentHash"
      setup_binary aiware-agent aiware-agent "$agentBinaryPath"
    fi
  }
}

# --- create killall script ---
create_killall() {
  [ "${INSTALL_AIWARE_BIN_DIR_READ_ONLY}" = true ] && return
  info "Creating killall script ..."
  $SUDO tee ${BIN_DIR}/${KILLALL_AIWARE_SH} >/dev/null <<\EOF
#!/bin/sh
set -x
[ $(id -u) -eq 0 ] || exec sudo $0 $@

for bin in /var/lib/aiware/data/**/bin/; do
    [ -d $bin ] && export PATH=$bin:$PATH
done

for service in /etc/systemd/system/aiware*.service; do
    [ -s $service ] && systemctl stop $(basename $service)
done


if $HAS_MACOSX; then
  launchctl stop ${SYSTEM_NAME}
fi


for service in /etc/init.d/aiware*; do
    [ -x $service ] && $service stop
done

# TODO - mac os x

# remove docker containers
docker ps -a | grep aiware | awk '{ print $1}' | xargs docker stop
docker ps -a | grep aiware | awk '{ print $1}' | xargs docker rm -f

EOF
  $SUDO chmod 755 ${BIN_DIR}/${KILLALL_AIWARE_SH}
  if $HAS_MACOSX; then
    $SUDO chown root:wheel ${BIN_DIR}/${KILLALL_AIWARE_SH}
  else
    $SUDO chown root:root ${BIN_DIR}/${KILLALL_AIWARE_SH}
  fi

  info "  Created script at $BIN_DIR/$KILLALL_AIWARE_SH"
}

# --- create upgrade script ---
create_upgrade() {
  [ "${INSTALL_AIWARE_BIN_DIR_READ_ONLY}" = true ] && return

  info "Creating upgrade script ..."
  $SUDO tee ${BIN_DIR}/${UPGRADE_AIWARE_SH} >/dev/null <<"EOF"
#!/bin/sh
#set -x

if [ $(id -un) != root ]; then
  echo "This script must be run as root, not '$(id -un)'"
  exit 2
fi

newVersion=
forceUpdate=false
while [ "$1" ]; do
  case "$1" in
    -f|--force)   forceUpdate=true;;
    --channel)    newVersion="INSTALL_AIWARE_CHANNEL=$2"; shift;;
    --channel=*)  newVersion="INSTALL_AIWARE_CHANNEL=${1#*=}";;
    --manifest)   newVersion="AIWARE_MANIFEST_VERSION=$2"; shift;;
    --manifest=*) newVersion="AIWARE_MANIFEST_VERSION=${1#*=}";;
    [1-9].*.*)    newVersion="AIWARE_MANIFEST_VERSION=$1";;
    *)            echo "Unknown version '$1'. Use --channel or --manifest to change version."; exit 3;;
  esac
  shift
done

# stops the currently running agent
stop_agent() {
  echo "Stopping aiware-agent"
  if [ "$(which systemctl)" ]; then
    systemctl stop aiware-agent
  elif [ "$(which launchctl)" ]; then
    launchctl stop aiware-agent
  elif [ "$(which rc-service)" ]; then
    rc-service aiware-agent stop
  fi
}

# restores the old configuration and restarts the agent
rollback_agent() {
  echo "ERROR: Upgrade failed: restarting previous version of aiware-agent"
  mv /tmp/aiware-agent.service.env.bak /etc/systemd/system/aiware-agent.service.env
  if [ "$(which systemctl)" ]; then
    systemctl start aiware-agent
  elif [ "$(which launchctl)" ]; then
    launchctl start aiware-agent
  elif [ "$(which rc-service)" ]; then
    rc-service aiware-agent start
  fi
}

# Recover disk space
timeout 300 docker system prune --force
df -h / /opt/aiware

# Check disk space
ROOT_DISK_SPACE=$(df --output=pcent / | grep -v "Use%" | tr -d ' %')
if [ "$ROOT_DISK_SPACE" -ge 98 ] ; then
   echo "Available space on root disk (/) is $((100-$ROOT_DISK_SPACE))% (must be >2% to upgrade)"
   $forceUpdate || exit 4
fi
DATA_DISK_SPACE=$(df --output=pcent /opt/aiware | grep -v "Use%" | tr -d ' %')
if [ "$DATA_DISK_SPACE" -ge 98 ] ; then
    echo "Available space on data disk (/opt/aiware) is $((100-$DATA_DISK_SPACE))% (must be >2% to upgrade)"
    $forceUpdate || exit 5
fi

stop_agent

aiwareMode=$(grep AIWARE_MODE /etc/systemd/system/aiware-agent.service.env | cut -d= -f2)
if echo ",${aiwareMode},"  | tr -d ' ' | grep -iq ',controller,'; then
  echo "Stopping and deleting Controller"
  docker stop -t60 aiware-controller
  docker rm -f aiware-controller
  docker rmi -f $(docker images | grep edge-controller | awk '{ print $3 }' | sort -u) 2>&1 || echo 'Controller image not found'
fi
if echo ",${aiwareMode}," | tr -d ' ' | grep -iq ',hubagent,'; then
  echo "Stopping and deleting Hub Agent"
  docker stop -t60 aiware-hub-agent
  docker rm -f aiware-hub-agent
  docker rmi -f $(docker images | grep hub-agent | awk '{ print $3 }' | sort -u) 2>&1 || echo 'Hub Agent image not found'
fi

# Clean up environment variables (but keep current environment as override)
echo "Cleaning up /etc/systemd/system/aiware-agent.service.env:"
{
  cat /etc/systemd/system/aiware-agent.service.env
  env | grep -E '^(AIWARE|INSTALL|VERITONE)_'
  if [ "$newVersion" ]; then
    echo unset INSTALL_AIWARE_CHANNEL
    echo unset AIWARE_MANIFEST_VERSION
    echo unset AIWARE_GIT_VERSION
    echo unset INSTALL_AIWARE_VERSION
    echo "$newVersion"
  fi
} | sed '/unset/!s/^/export /' >/tmp/aiware-agent.service.env
mv /etc/systemd/system/aiware-agent.service.env /tmp/aiware-agent.service.env.bak
. /tmp/aiware-agent.service.env
env | grep -E '^(AIWARE|INSTALL|VERITONE)_'

if [ "${AIWARE_MODE}" = "" ]; then
  echo "No AIWARE_MODE defined"
  rollback_agent
  exit 6
fi

# see if we need to read the latest manifest version number
[ "$AIWARE_MANIFEST_VERSION" = "latest" ] && {
  latestManifest=$(curl -s https://get.aiware.com/aiware-versions/${AIWARE_MANIFEST_TIER:-stable}/aiware-latest.txt)
  [ "$latestManifest" ] || {
    echo "No latest '${AIWARE_MANIFEST_TIER}' version number found. Please make sure AIWARE_MANIFEST_TIER is correct and try again"
    rollback_agent
    exit 8
  }
  echo "Replacing aiWARE version 'latest' with '$latestManifest'"
  export AIWARE_MANIFEST_VERSION=${latestManifest}
}

installScript=install.sh
[ "$INSTALL_AIWARE_CHANNEL" = "stage" ] && installScript="install.sh-${INSTALL_AIWARE_CHANNEL}"
[ "$INSTALL_AIWARE_CHANNEL" = "dev" ] && installScript="install.sh-${INSTALL_AIWARE_CHANNEL}"
[ "$INSTALL_AIWARE_VERSION" ] && installScript="dist/install.sh-${INSTALL_AIWARE_VERSION}"
[ "$AIWARE_MANIFEST_VERSION" ] && {
  # use the installer that matches the edge-agent from the manifest
  # note: 'dev' contains a superset of all versions and the installer will validate the tier so we don't have to
  agentHash=$(curl -s https://get.aiware.com/aiware-versions/dev/aiware-${AIWARE_MANIFEST_VERSION}.yaml | grep edge-agent | head -n1 | cut -d':' -f2 | tr -d ' ')
  [ "$agentHash" ] || {
      # this is impossible if manifests are being validated correctly
      echo "No hash version for edge-agent found in version $AIWARE_MANIFEST_VERSION"
      rollback_agent
      exit 7
  }
  installScript="dist/builds/agent/install.sh-${agentHash}"
}

echo "Running install script https://get.aiware.com/${installScript}"
curl -sfl https://get.aiware.com/${installScript} | sh

if [ -f /etc/systemd/system/aiware-agent.service.env ]; then
  # clean up
  rm /tmp/aiware-agent.service.env.bak
else
  rollback_agent
fi
exit 0
EOF
  $SUDO chmod 755 ${BIN_DIR}/${UPGRADE_AIWARE_SH}
  if $HAS_MACOSX; then
    $SUDO chown root:wheel ${BIN_DIR}/${UPGRADE_AIWARE_SH}
  else
    $SUDO chown root:root ${BIN_DIR}/${UPGRADE_AIWARE_SH}
  fi
  info "  Created script at ${BIN_DIR}/${UPGRADE_AIWARE_SH}"
}

# --- create uninstall script ---
create_uninstall() {
  [ "${INSTALL_AIWARE_BIN_DIR_READ_ONLY}" = true ] && return
  info "Creating uninstall script ..."
  $SUDO tee ${BIN_DIR}/${UNINSTALL_AIWARE_SH} >/dev/null <<EOF
#!/bin/sh
set -x
[ \$(id -u) -eq 0 ] || exec sudo \$0 \$@

${BIN_DIR}/${KILLALL_AIWARE_SH}

if $HAS_MACOSX; then
  launchctl stop ${SYSTEM_NAME}
  launchctl unload ${FILE_AIWARE_SERVICE}
  rm /Library/LaunchDaemons/${SYSTEM_NAME}.plist
fi

if which systemctl; then
    systemctl disable ${SYSTEM_NAME}
    systemctl reset-failed ${SYSTEM_NAME}
    systemctl daemon-reload
fi
if which rc-update; then
    rc-update delete ${SYSTEM_NAME} default
fi

rm -f ${FILE_AIWARE_SERVICE}
rm -f ${FILE_AIWARE_ENV}
mv /var/run/aiware-agent.json /var/run/aiware-agent.json.bak

rm -f ${BIN_DIR}/${SYSTEM_NAME}
rm -f ${BIN_DIR}/ai
rm -f ${HOME}/.config/aiware-cli.yaml || true
rmdir ${HOME}/.config || true
rm -rf /opt/aiware/*

remove_uninstall() {
    rm -f ${BIN_DIR}/${UNINSTALL_AIWARE_SH} || true
}
trap remove_uninstall EXIT

if (ls ${SYSTEMD_DIR}/aiware*.service || ls /etc/init.d/aiware*) >/dev/null 2>&1; then
    set +x; info 'Additional aiware services installed, skipping uninstall of aiware'; set -x
    exit
fi

# remove docker containers and images
info "Removing docker contains"
docker ps -a | grep aiware | awk '{ print $1}' | xargs docker stop
docker ps -a | grep aiware | awk '{ print $1}' | xargs docker rm -f
docker images | grep aiware | awk '{ print $3 }' | xargs docker rmi -f

# remove aiware volumes
docker volume ls | grep aiware | awk '{ print $2 }' | xargs docker volume rm

info "Removing files"
rm -rf /etc/aiware || true
rm -rf /usr/local/aiware || true
rm -rf /var/lib/aiware || true
rm -f ${BIN_DIR}/aiware || true
rm -f ${BIN_DIR}/venti || true
rm -f ${BIN_DIR}/${KILLALL_AIWARE_SH} || true
rm -f ${BIN_DIR}/${USAGE_AIWARE_SH} || true
rm -f ${BIN_DIR}/${UPGRADE_AIWARE_SH} || true

EOF
  $SUDO chmod 755 ${BIN_DIR}/${UNINSTALL_AIWARE_SH}
  if $HAS_MACOSX; then
    $SUDO chown root:wheel ${BIN_DIR}/${UNINSTALL_AIWARE_SH}
  else
    $SUDO chown root:root ${BIN_DIR}/${UNINSTALL_AIWARE_SH}
  fi
  info "  Created script at ${BIN_DIR}/${UNINSTALL_AIWARE_SH}"
}

# --- create usage script (on db host) ---
create_usage() {
  has_mode db || return
  [ "${INSTALL_AIWARE_BIN_DIR_READ_ONLY}" = true ] && return

  info "Downloading usage script ..."
  if curl -sfL https://get.aiware.com/usage.sh >"${BIN_DIR}/${USAGE_AIWARE_SH}"; then
    $SUDO chmod 755 "${BIN_DIR}/${USAGE_AIWARE_SH}"
    info "Usage created"
    if $HAS_MACOSX; then
      $SUDO chown root:wheel ${BIN_DIR}/${USAGE_AIWARE_SH}
    else
      $SUDO chown root:root ${BIN_DIR}/${USAGE_AIWARE_SH}
    fi
    info "  Created ${BIN_DIR}/${USAGE_AIWARE_SH} script"
  else
    info "  Unable to download the usage script from https://get.aiware.com/usage.sh"
  fi
}

# --- disable current service if loaded --
systemd_disable() {
  $SUDO rm -f /etc/systemd/system/${SERVICE_AIWARE} || true
  #$SUDO rm -f /etc/systemd/system/${SERVICE_AIWARE}.env || true
  $SUDO systemctl disable ${SYSTEM_NAME} >/dev/null 2>&1 || true

  if $HAS_MACOSX; then
    $SUDO launchctl stop ${FILE_AIWARE_SERVICE}
    $SUDO launchctl unload ${FILE_AIWARE_SERVICE}
  fi
}

# --- capture current env and create file containing aiware_ variables ---
create_env_file() {
  [ "${INSTALL_AIWARE_SKIP_SERVICE}" = true ] && return

  info "env: Creating environment file ${FILE_AIWARE_ENV}"

  if $HAS_MACOSX; then
    info 'Not setting umask for mac os x'
  else
    UMASK=$(umask)
    umask 0377
  fi

  info 'Creating aiware-agent.env file and echoing to screen'
  env | grep '^AIWARE_' | $SUDO tee -a ${FILE_AIWARE_ENV}
  env | grep '^INSTALL_' | $SUDO tee -a ${FILE_AIWARE_ENV}
  env | grep '^VERITONE_' | $SUDO tee -a ${FILE_AIWARE_ENV}
  info 'End of environment variables'

  if [ "${UMASK}" != "" ]; then
    info "Setting umask: ${UMASK}"
    umask $UMASK
  fi

}

# --- write systemd service file ---
create_systemd_service_file() {
  [ "${INSTALL_AIWARE_SKIP_SERVICE}" = true ] && return
  info "systemd: Creating service file ${FILE_AIWARE_SERVICE}"
  $SUDO tee ${FILE_AIWARE_SERVICE} >/dev/null <<EOF
[Unit]
Description=aiWARE Edge
Documentation=https://aiware.com
After=network-online.target

[Service]
Type=${SYSTEMD_TYPE}
EnvironmentFile=${FILE_AIWARE_ENV}
ExecStart=${BIN_DIR}/aiware-agent ${CMD_AIWARE_EXEC}


KillMode=process
Delegate=yes
LimitNOFILE=infinity
LimitNPROC=infinity
LimitCORE=infinity
TasksMax=infinity
TimeoutStartSec=0
Restart=always
RestartSec=5s

[Install]
WantedBy=multi-user.target
EOF
}

# --- write openrc service file ---
create_openrc_service_file() {
  LOG_FILE=/var/log/${SYSTEM_NAME}.log

  info "openrc: Creating service file ${FILE_AIWARE_SERVICE}"
  $SUDO tee ${FILE_AIWARE_SERVICE} >/dev/null <<EOF
#!/sbin/openrc-run

depend() {
    after network-online
}

start_pre() {
    rm -f /tmp/aiware.*
}

supervisor=supervise-daemon
name=${SYSTEM_NAME}
command="${BIN_DIR}/aiware-agent"
command_args="$(escape_dq "${CMD_AIWARE_EXEC}")
    >>${LOG_FILE} 2>&1"

output_log=${LOG_FILE}
error_log=${LOG_FILE}

pidfile="/var/run/${SYSTEM_NAME}.pid"
respawn_delay=5

set -o allexport
if [ -f /etc/environment ]; then source /etc/environment; fi
if [ -f ${FILE_AIWARE_ENV} ]; then source ${FILE_AIWARE_ENV}; fi
set +o allexport
EOF
  $SUDO chmod 0755 ${FILE_AIWARE_SERVICE}

  $SUDO tee /etc/logrotate.d/${SYSTEM_NAME} >/dev/null <<EOF
${LOG_FILE} {
        missingok
        notifempty
        copytruncate
}
EOF
}

# --- write macosx service file ---
create_macosx_service_file() {
  LOG_FILE=/var/log/${SYSTEM_NAME}.log

  info "macosx: Creating service file ${FILE_AIWARE_SERVICE}"
  $SUDO tee ${FILE_AIWARE_SERVICE} >/dev/null <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
        <key>Label</key>
        <string>aiware-agent</string>
        <key>Program</key>
        <string>${FILE_AIWARE_SCRIPT}</string>
        <key>ProgramArguments</key>
        <array>
          <string>${FILE_AIWARE_SCRIPT}</string>
        </array>
        <key>RunAtLoad</key>
        <true/>
        <key>StandardErrorPath</key>
        <string>/dev/null</string>
        <key>StandardOutPath</key>
        <string>${LOG_FILE}</string>
</dict>
</plist>
EOF

  $SUDO tee ${FILE_AIWARE_SCRIPT} >/dev/null <<EOF
#!/bin/sh

source ${FILE_AIWARE_ENV}
export \$(cat ${FILE_AIWARE_ENV} | xargs)


${BIN_DIR}/aiware-agent $(escape_dq "${CMD_AIWARE_EXEC}")

EOF

  $SUDO chmod 0644 ${FILE_AIWARE_SERVICE}
  $SUDO chmod 0755 ${FILE_AIWARE_SCRIPT}
}

# --- write systemd or openrc service file ---
create_service_file() {
  [ "${INSTALL_AIWARE_SKIP_SERVICE}" = true ] && return

  # Install the right version
  $HAS_SYSTEMD && create_systemd_service_file
  $HAS_OPENRC && create_openrc_service_file
  $HAS_MACOSX && create_macosx_service_file

  return 0
}

# --- Create the opt directory structures
create_opt_aiware() {
  info 'Making /opt/aiware'
  $SUDO mkdir -p /opt/aiware || true
  $SUDO mkdir -p /opt/aiware/postgres /opt/aiware/nfs /opt/aiware/registry /opt/aiware/prometheus || true

  if [ "${AIWARE_CACHE}" != "" ]; then
    if command -v timeout >/dev/null 2>&1; then
      $SUDO timeout 10 mkdir -p $AIWARE_CACHE
      if [ $? -eq 124 ]; then
        # recover hung nfs-mounted volume
        $SUDO umount -l $AIWARE_CACHE
        $SUDO timeout 10 mkdir -p $AIWARE_CACHE
      fi
    else
      $SUDO mkdir -p $AIWARE_CACHE
    fi
  fi
}

# --- get hashes of the current aiware-agent bin and service files
get_installed_hashes() {
  $SUDO run_shasum ${BIN_DIR}/aiware-agent ${FILE_AIWARE_SERVICE} ${FILE_AIWARE_ENV} 2>&1 || true
}

# --- enable and start systemd service ---
systemd_enable() {
  [ "${INSTALL_AIWARE_SKIP_SERVICE}" = true ] && return
  info "systemd: Enabling ${SYSTEM_NAME} unit"
  $SUDO systemctl enable ${FILE_AIWARE_SERVICE} >/dev/null
  $SUDO systemctl daemon-reload >/dev/null
}

systemd_start() {
  [ "${INSTALL_AIWARE_SKIP_SERVICE}" = true ] && return
  info "systemd: Starting ${SYSTEM_NAME}"
  $SUDO systemctl restart ${SYSTEM_NAME}
}

# --- enable and start openrc service ---
openrc_enable() {
  [ "${INSTALL_AIWARE_SKIP_SERVICE}" = true ] && return
  info "openrc: Enabling ${SYSTEM_NAME} service for default runlevel"
  $SUDO rc-update add ${SYSTEM_NAME} default >/dev/null
}

openrc_start() {
  [ "${INSTALL_AIWARE_SKIP_SERVICE}" = true ] && return
  info "openrc: Starting ${SYSTEM_NAME}"
  $SUDO ${FILE_AIWARE_SERVICE} restart
}

# --- enable and start openrc service ---
macosx_enable() {
  [ "${INSTALL_AIWARE_SKIP_SERVICE}" = true ] && return
  info "macosx: Enabling ${SYSTEM_NAME} service for default runlevel"
  $SUDO launchctl load ${FILE_AIWARE_SERVICE}
}

macosx_start() {
  [ "${INSTALL_AIWARE_SKIP_SERVICE}" = true ] && return
  info "macosx: Starting ${SYSTEM_NAME}"
  $SUDO launchctl start ${SYSTEM_NAME}
}

set_defaults() {
  $HAS_MACOSX && macosx_set_defaults
}

macosx_set_defaults() {
  if [ -z "$(printenv | grep AIWARE)" ]; then
    info "No install variables set. Using the defaults"
    info "Retriving the IP address"
    IPADDR=$(ifconfig | grep inet | grep -v inet6 | grep -v "169.254" | grep -v 127.0.0.1 | head -n1 | awk '{ print $2 }')
    info "Using IP address of $IPADDR"
    info 'Making $HOME/aiware'
    mkdir -p $HOME/aiware/root $HOME/aiware/cache || true
    export AIWARE_MODE=controller,db,api,lb,engine,redis,prometheus,minio,nsq,es
    export AIWARE_DB_PORT=5432
    export AIWARE_CACHE=$HOME/aiware/cache
    export AIWARE_DB_ROOT=$HOME/aiware/root/postgres
    export AIWARE_REGISTRY_ROOT=$HOME/aiware/root/registry
    export AIWARE_ROOT=$HOME/aiware/root
    export AIWARE_AGENT_UPDATE_INTERVAL=15
    export AIWARE_RUN_CONFIG=$HOME/aiware/aiware-config.json
    export AIWARE_HOST_EXPIRE=false
    export AIWARE_INIT_TOKEN=$(uuidgen)
    export AIWARE_CONTROLLER=http://$IPADDR:9000/edge/v1
    info "AIWARE_INIT_TOKEN is $AIWARE_INIT_TOKEN"
  fi
}

# --- startup systemd or openrc service ---
service_enable_and_start() {
  [ "${INSTALL_AIWARE_SKIP_SERVICE}" = true ] && return

  $HAS_SYSTEMD && systemd_enable
  $HAS_OPENRC && openrc_enable
  $HAS_MACOSX && macosx_enable

  [ "$INSTALL_AIWARE_SKIP_START" = true ] && {
    warn "Skipping service start (because of INSTALL_AIWARE_SKIP_START)"
    return
  }

  POST_INSTALL_HASHES=$(get_installed_hashes)
  if [ "${PRE_INSTALL_HASHES}" = "${POST_INSTALL_HASHES}" ]; then
    info 'No change detected so skipping service start'
    return
  fi

  $HAS_SYSTEMD && systemd_start
  $HAS_OPENRC && openrc_start
  $HAS_MACOSX && macosx_start
  return 0
}

# --- re-evaluate args to include env command ---
eval set -- $(escape "${INSTALL_AIWARE_EXEC}") $(quote "$@")

# --- install k8s ---
# Complete end state should look like this

#######################################
# kube-system        metrics-server-86cbb8457f-rn7qz           1/1     Running     0          3m55s
# kube-system        local-path-provisioner-5ff76fc89d-7rtbj   1/1     Running     0          3m55s
# kube-system        coredns-854c77959c-xxnkv                  1/1     Running     0          3m55s
# knative-serving    controller-57956677cf-j88n4               1/1     Running     0          3m26s
# knative-serving    webhook-ff79fddb7-ml2j5                   1/1     Running     0          3m26s
# knative-serving    autoscaler-75895c6c95-bfj9s               1/1     Running     0          3m26s
# knative-serving    activator-799bbf59dc-qv2jw                1/1     Running     0          3m26s
# istio-system       svclb-istio-ingressgateway-4kctb          5/5     Running     0          2m53s
# istio-system       istiod-7b688587d8-pr556                   1/1     Running     0          2m54s
# istio-system       istio-ingressgateway-564b5ffdf7-4xt8p     1/1     Running     0          2m54s
# knative-serving    istio-webhook-5f876d5c85-ctbhx            1/1     Running     0          2m1s
# knative-serving    networking-istio-6bbc6b9664-d48z8         1/1     Running     0          2m1s
# knative-serving    default-domain-l6vzj                      0/1     Completed   0          97s
# knative-eventing   eventing-controller-674d4785c-nhj8p       1/1     Running     0          29s
# knative-eventing   eventing-webhook-55d56cc446-kbvqk         1/1     Running     0          29s
#
#######################################

generate_k3s_install_config() {
  if [ -f "/etc/rancher/k3s/config.yaml" ]; then
    info "k3s config file already present at [/etc/rancher/k3s/config.yaml], using this config."
    return
  fi

  info "Generating k3s install config at [/etc/rancher/k3s/config.yaml]..."
  # generate file
  mkdir -p /etc/rancher/k3s
  touch /etc/rancher/k3s/config.yaml

  # Set cluster init for master and server + token for non-master
  if [ "$K3S_SERVER_NODE" = "true" ]; then
    echo "cluster-init: true" >>/etc/rancher/k3s/config.yaml
    echo "token: $K3S_TOKEN" >>/etc/rancher/k3s/config.yaml
  else
    echo "server: $K3S_URL" >>/etc/rancher/k3s/config.yaml
    echo "token: $K3S_TOKEN" >>/etc/rancher/k3s/config.yaml
  fi

  # Set networking options for all nodes in the control plane (Master + Backup Controllers)
  if [ "$K3S_SERVER_NODE" = "true" ] || [ ! -z "$K3S_BACKUP_CONTROLLER" ]; then
    echo "disable: traefik" >>/etc/rancher/k3s/config.yaml
    echo "cluster-cidr: 10.128.2.0/20" >>/etc/rancher/k3s/config.yaml
    echo "service-cidr: 10.128.17.0/24" >>/etc/rancher/k3s/config.yaml
  fi

  # Label the node with runmodes & additional labels
  {
    if [ ! -z "$AIWARE_MODE" ] || [ ! -z "$AIWARE_K8S_NODE_LABELS" ]; then
      echo "node-label:" >> /etc/rancher/k3s/config.yaml
    fi

    # loop supplemental labels provided and add labels
    for label in $(echo $AIWARE_K8S_NODE_LABELS | sed "s/,/ /g")
    do
      echo "  - '$label'" >> /etc/rancher/k3s/config.yaml
    done

    # loop runmodes and add each as a label
    for run_mode in $(echo $AIWARE_MODE | sed "s/,/ /g")
    do
      # prefix run_mode with "runmode-" and save into a variable
      runmode=runmode-$run_mode
      echo "  - '$runmode=true'" >> /etc/rancher/k3s/config.yaml
    done

    # check if $AIWARE_K8S_NODE_TAINTS exists, if it does then add taints to the config
    if [ ! -z "$AIWARE_K8S_NODE_TAINTS" ]; then
      echo "node-taint:" >> /etc/rancher/k3s/config.yaml
      # loop supplemental taints provided and add taints
      for taint in $(echo $AIWARE_K8S_NODE_TAINTS | sed "s/,/ /g")
      do
        echo "  - '$taint'" >> /etc/rancher/k3s/config.yaml
      done
    fi
  }

  info "Generated Config for k3s install: "
  info "#============ k3s Install Config ============#"
  cat /etc/rancher/k3s/config.yaml
  info "#============================================#"
}

install_k3s() {
  info "####################################### Installing k3s #######################################"

  if [ "$INSTALL_AIWARE_GEN_K3S_CFG" = "true" ]; then
    generate_k3s_install_config
  fi

  if [ "$K3S_SERVER_NODE" = "true" ]; then
    info "Initializing k3s cluster mode"
    curl -sfL https://get.k3s.io | sh -
    # TOKEN=$(cat /var/lib/rancher/k3s/server/node-token)
    # printf "K3S_TOKEN=$TOKEN\n"
    info "waiting for k3s to become ready"
    sleep 30
    kubectl get nodes
    info "k3s installed - above output should show master node ready"
  else
    if [ -z "$K3S_URL" ]; then
      fatal "ERROR: Need to have the K3S_URL environment variable set to something like K3S_URL=https://<ip of master node>:6443"
    fi
    if [ -z "$K3S_BACKUP_CONTROLLER" ]; then
      info "Attempting to join k3s cluster with url $K3S_URL and token $K3S_TOKEN as worker node"
      curl -sfL https://get.k3s.io | sh -
    else
      info "Attempting to join k3s cluster with url $K3S_URL and token $K3S_TOKEN as backup controller node"
      curl -sfL https://get.k3s.io | sh -s - server
      info "waiting for k3s to become ready"
      sleep 30
      kubectl get nodes
      info "k3s installed - above output should show master node ready"
    fi
  fi
}

install_k8_aws() {
  info "k8s -> join ec2 to eks cluster"
}

install_helm() {
  info "####################################### Installing helm #######################################"

  if ! command -v helm &>/dev/null; then
    info "helm could not be found... installing."
    curl https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bash
  else
    info "helm installation found."
  fi

  helm repo update
}

install_knative_cli() {
  if [ "$INSTALL_AIWARE_KNATIVE" = "true" ]; then
    info "####################################### Installing knative cli #######################################"

    unameOut="$(uname -s)"
    case "${unameOut}" in
    Linux*) machine=Linux ;;
    Darwin*) machine=Mac ;;
    CYGWIN*) machine=Cygwin ;;
    MINGW*) machine=MinGw ;;
    *) machine="UNKNOWN:${unameOut}" ;;
    esac
    info "machine is ${machine}"

    if ! command -v kn &>/dev/null; then
      info "Installing Knative"
      CWD=$PWD
      cd /tmp
      wget https://storage.googleapis.com/knative-nightly/client/latest/kn-linux-amd64
      chmod +x kn-linux-amd64
      mv kn-linux-amd64 /usr/local/bin/kn
      info "kn cli installed"
      cd $CWD
    else
      info "Knative cli installation found."
    fi
  fi
}

install_knative() {
  if [ "${INSTALL_AIWARE_KNATIVE}" = "true" ]; then
    info "####################################### Installing knative serving #######################################"

    kubectl apply -f https://github.com/knative/serving/releases/download/v0.25.0/serving-crds.yaml
    kubectl apply -f https://github.com/knative/serving/releases/download/v0.25.0/serving-core.yaml
    kubectl apply -l knative.dev/crd-install=true -f https://github.com/knative/net-istio/releases/download/v0.25.0/istio.yaml
    kubectl apply -f https://github.com/knative/net-istio/releases/download/v0.25.0/istio.yaml
    kubectl apply -f https://github.com/knative/net-istio/releases/download/v0.25.0/net-istio.yaml
    kubectl apply -f https://github.com/knative/serving/releases/download/v0.25.0/serving-default-domain.yaml
    kubectl apply -f https://github.com/knative/serving/releases/download/v0.25.0/serving-hpa.yaml
    kubectl apply -f https://github.com/knative/eventing/releases/download/v0.25.0/eventing-crds.yaml
    kubectl apply -f https://github.com/knative/eventing/releases/download/v0.25.0/eventing-core.yaml
    kubectl get pods --all-namespaces -o wide

    # k3s puts kube config in a weird spot. copy it to it's regular spot for kn cli
    cp /etc/rancher/k3s/k3s.yaml ~/.kube/config
    info "Knative installed and ready"
  fi
}

install_kubectl() {
  if ! command -v kubectl &>/dev/null; then
    curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
    curl -LO "https://dl.k8s.io/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl.sha256"
    echo "$(<kubectl.sha256) kubectl" | sha256sum --check
    sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl
    kubectl version --client
    info "installed kubectl"
  else
    info "kubectl found."
  fi

}

install_k8s() {
  if [ "$INSTALL_AIWARE_K8S" = "true" ]; then
    if [ "$INSTALL_AIWARE_K8S_MODE" = "aws" ]; then
      install_k8_aws
      info "Installing k8s"
    else
      if [ -z $K3S_SERVER_NODE ]; then
        # if not set, then have it create a k3s server node, this is not
        # for a hybrid cluster as the setup scripts will do this correctly
        export K3S_SERVER_NODE=true
      fi
      if [ -z $K3S_TOKEN ]; then
        # if not set then generate a token, this is not
        # for a hybrid cluster as the setup scripts will do this correctly
        export K3S_TOKEN=$(uuidgen)
      fi
      install_k3s
    fi
    install_helm
    # install_knative_cli
    # install_knative
    info "####################################### aiWARE Services Installed #######################################"

    if [ "$K3S_SERVER_NODE" = "true" ] && [ "$INSTALL_AIWARE_K8S_MODE" != "aws" ]; then
      info "Installed k3s"
      # TOKEN=$(cat /var/lib/rancher/k3s/server/node-token)
      # setup the external ip address by first detecting the default route and then querying ifconfig
      INTERFACE=$(route | grep '^default' | grep -o '[^ ]*$')
      EXTERNAL_IP=$(ifconfig $INTERFACE | grep 'inet ' | cut -d: -f2 | awk '{ print $2}')

      printf "To cluster other kubernetes nodes to this machine, export these variables before running the AIWARE installation script on another machine:\n"
      printf "export INSTALL_AIWARE_K8S=true\n"
      # printf "export K3S_TOKEN=$TOKEN \n"
      printf "export K3S_URL=https://$EXTERNAL_IP:6443\n"
      printf "Please double check this ip address. it may be a public ip instead of a private ip\n"
    fi
  else
    info "Not installing k8s. To install, do 'export INSTALL_AIWARE_K8S=true'"
  fi

  # Install kubectl if base64 of kubeconfig is set
  if [ "$INSTALL_AIWARE_KUBECONFIG" = "true" ]; then
    install_kubectl
    # install_knative_cli
    mkdir -p ~/.kube
    echo $INSTALL_AIWARE_KUBECONFIG | base64 --decode >~/.kube/config
    info "Installed kube config."

    if [ "$AIWARE_COPY_KUBECONFIG" = "true" ]; then
      info "Copying kubeconfig to ~/.kube/config for use with kubectl"
      cp /etc/rancher/k3s/k3s.yaml ~/.kube/config
    fi
  fi
}

# install the uuidgen tool. will exit if the install fails. For OSX 10.4 and newer, uuidgen is installed by default.
install_uuidgen() {
  info 'Installing uuidgen'
  if $HAS_APT; then
    apt-get install uuid-runtime
  elif $HAS_DNF; then
    dnf install util-linux
  elif $HAS_YUM; then
    yum install util-linux
  else
    fatal 'Please install "uuidgen" and try again.'
  fi

  retVal=$?
  if [ $retVal -ne 0 ]; then
    fatal 'Failed to install "uuidgen". Please install "uuidgen" and try again.'
  fi
}

# generate a token if one isn't already set.
set_aiware_init_token() {
  # skip if we don't need the token or if token is already set.
  [ "${INSTALL_AIWARE_SKIP_SERVICE}" = true ] && return
  [ "${AIWARE_INIT_TOKEN}" != "" ] && return

  # install uuidgen if it's not there
  if ! command -v uuidgen >/dev/null 2>&1; then
    install_uuidgen
  fi

  info 'Generating AIWARE_INIT_TOKEN'
  export AIWARE_INIT_TOKEN=$(uuidgen)
  info "AIWARE_INIT_TOKEN is $AIWARE_INIT_TOKEN"
}

# generate a cli yaml config if one isn't present
create_cli_yaml_config() {
  # Check for config at $HOME/.config/aiware-cli.yaml
  CFG_FILE="aiware-cli.yaml"
  ROOT_CLI_CONFIG_PATH="$HOME/.config"
  USER_HOME=$(eval echo ~$SUDO_USER)
  USER_CLI_CONFIG_PATH="$USER_HOME/.config"

  # Skip if already present
  [ -f "$ROOT_CLI_CONFIG_PATH/$CFG_FILE" ] && return
  [ -f "$USER_CLI_CONFIG_PATH/$CFG_FILE" ] && return

  if [ ! -d "$ROOT_CLI_CONFIG_PATH" ]; then
    mkdir -p $ROOT_CLI_CONFIG_PATH
  fi

  if [ ! -d "$USER_CLI_CONFIG_PATH" ]; then
    mkdir -m 0755 -p $USER_CLI_CONFIG_PATH
  fi

  # If this is a CLI Only install ensure that AIWARE_CONTROLLER and AIWARE_INIT_TOKEN are defaulted if not set
  if [ "${INSTALL_AIWARE_SKIP_SERVICE}" = true ]; then
    if [  -z "${AIWARE_CONTROLLER}" ]; then
      IPADDR=$(ifconfig | grep inet | grep -v inet6 | grep -v "169.254" | grep -v 127.0.0.1 | head -n1 | awk '{ print $2 }')
      AIWARE_CONTROLLER=http://$IPADDR:9000/edge/v1
    fi

    if [ -z "${AIWARE_INIT_TOKEN}" ]; then
      AIWARE_INIT_TOKEN=$(uuidgen)
    fi
  fi

  $SUDO tee ${ROOT_CLI_CONFIG_PATH}/${CFG_FILE} ${USER_CLI_CONFIG_PATH}/${CFG_FILE} >/dev/null <<EOF
---
profiles:
 default:
  url: ${AIWARE_CONTROLLER}
  token: ${AIWARE_INIT_TOKEN}
EOF

  info "Generated a CLI Config for User:$SUDO_USER at $USER_CLI_CONFIG_PATH/$CFG_FILE and User:root at $ROOT_CLI_CONFIG_PATH/$CFG_FILE"
}

# --- run the install process --
{
  intro
  verify_run_modes
  check_installer
  verify_system
  verify_system_docker
  set_defaults
  set_aiware_init_token
  install_ifconfig
  install_k8s
  setup_env "$@"
  download_and_verify
  create_killall
  create_upgrade
  create_uninstall
  create_usage
  systemd_disable
  create_opt_aiware
  create_env_file
  create_service_file
  service_enable_and_start
  create_cli_yaml_config

  info "aiWARE version ${VERSION_AIWARE} installed"
} 2>&1 | tee "$LOGFILE"
