#!/bin/bash

set -o errexit  # set -e
set -o errtrace  # set -E
set -o pipefail  # If set, the return value of a pipeline is the value of the last (rightmost) command to exit with a non-zero status, or zero if all commands in the pipeline exit successfully. This option is disabled by default.
# set -o nounset  # set -u
# set -o xtrace  # set -x
# set -o functrace

branch="master"
want_debug=0
want_precompile=1
run_init=0
list_versions=0
keep_build_name=1
create_indexes=0
download_only=0
deployment_path_packages="/opt/plugandwork/packages"
deployment_path="/opt/plugandwork/apps/rce"
shared_path="/opt/plugandwork/shared"
ruby_version='2.6.6'  # default version
bundler_version='2.5.13'
nvm_version='0.39.7'  # currently unused. In case we may want to download https://raw.githubusercontent.com/nvm-sh/nvm/v${nvm_version}/install.sh
nodejs_version='20.15.1'  # default version
cleanup_ruby=0
update_ruby=0
update_nodejs=0
args=()
initial_args=$@

SCRIPTS_DIR="/opt/plugandwork/bin"

# When run from Puppet as 'webapp', it has no $HOME set. When run from Puppet as root, $HOME is strangely still not set though paw_agent will do a 'su --login webbap'
# NVM install needs $HOME
test -n "${HOME}" || export HOME="$(getent passwd $(id -un) | cut -d: -f6)"


###
#   FUNCTIONS
###

function_exists() {
    declare -f -F $1 >/dev/null
    return $?  # probably unnecessary
}


# Print usage
usage() {
  >&2 echo -n "${SCRIPTS_DIR}/paw_agent [OPTION]... [FILE]...
Description of this script.
 Options:
  -l, --list-versions List available versions from the repository
  -p, --path          Base path of the installation. Default is /opt/plugandwork/apps/rce
  -v, --version       version of the plugandwork core archive to download
                      and install (default is latest)
  -c, --customer      select the customer's package
  --create-indexes    Create mongo indexes (do not download nor install anything else)
  --cleanup-ruby      Delete other Ruby versions
  --update-ruby       You want to make sure RVM Ruby is updated
  --update-nodejs     You want to make sure NVM Nodejs is installed and updated
  --dev               Choose a development version (default is from master branch)
  --debug             Enable debug script tracing mode
  --download-only     Only download the selected archive, without installing it
  --init              Run ${SCRIPTS_DIR}/paw_init script to initialize system
  --latest            Choose latest version from the repository
  --no-precompile     No assets:precompile and paw_restart after package install
  --package           Turn on package deployment mode. This will deploy a specific
  --staging           Choose a staging version (default is from master branch)
                      application package rather than the core application
  --update-ruby       will update Ruby if necessary. Useful only for core<4 or if --download-only
"
}  # usage()


out() {
  ((quiet)) && return

  local message="$@"
  if ((piped)); then
    message=$(echo $message | sed '
      s/\\[0-9]\{3\}\[[0-9]\(;[0-9]\{2\}\)\?m//g;
      s/✖/Error:/g;
      s/✔/Success:/g;
    ')
  fi
  printf '%b\n' "$message";
}  # out()


die() {
  echo
  out "$@"
  # function_exists 'on_exit' && on_exit  # necessary only if EXIT is catched
  exit 1
} >&2


# Set a trap for cleaning up in case of errors or when script exits.
rollback() {
  MYSELF="$0"               # equals to my script name
  LASTLINE="$1"            # argument 1: last line of error occurence
  LASTERR="$2"             # argument 2: error code of last command

  # Print error context
  echo >&2 "Error - exited with status $LASTERR at line $LASTLINE:";
         pr -tn $0 | tail -n+$((LASTLINE - 3)) | head -n7

  # Print big warning that some error happened
  echo -e "\e[31m
                                             __
                                 _          / /
   ___  _ __  _ __  ___   _ __  (_)______  / /
  / _ \| '__|| '__|/ _ \ | '__|   |______|/ /
 |  __/| |   | |  | (_) || |     _       / /
  \___||_|   |_|   \___/ |_|    (_)     /_/

  Seems like something wrong happened. Check logs !
\e[0m"

  if test -e "${deployment_path}"/current.in_case_of_rollback -o -L "${deployment_path}"/current.in_case_of_rollback ; then
    test -L "${deployment_path}"/current.in_case_of_rollback && rollback_to_dir_string="to "`readlink "${deployment_path}"/current.in_case_of_rollback`

    echo -ne >&2 "\e[32mRolling 'current' back ${rollback_to_dir_string} ...\e[0m";
    rm -rf "${deployment_path}"/current 2>/dev/null || true
    mv "${deployment_path}"/current.in_case_of_rollback "${deployment_path}"/current
    echo -e >&2 "\e[32mDone\e[0m"
  else
    echo -ne >&2 "\e[32m'current' can not or does not need rollback\e[0m";
    test -L "${deployment_path}"/current && echo >&2 "and still links to "`readlink "${deployment_path}"/current`
    echo >&2 ''
  fi

  die
}  # rollback()


list_available_versions() {

  if [[ ! -z ${package} ]]; then
    board_subfolder=${package}
    artifact_name=${package}
  else
    board_subfolder=core
    artifact_name=plugandwork-core
  fi

  if [[ -n "${customer}" ]]; then
    board_subfolder=${board_subfolder}${customer_dash}
    artifact_name=${artifact_name}${customer_dash}
  fi

  dev_versions=$(curl -s http://board.plugandwork.net/${board_subfolder}/dev/ | grep -v "Index" | grep "${artifact_name}" | cut -d '>' -f2 | cut -d '<' -f1 | sed "s%${artifact_name}-%%g" | sed 's%\.tar\.gz%%g' | cut -d- -f1 | sort -n | uniq || true)
  staging_versions=$(curl -s http://board.plugandwork.net/${board_subfolder}/staging/ | grep -v "Index" | grep "${artifact_name}" | cut -d '>' -f2 | cut -d '<' -f1 | sed "s%${artifact_name}-%%g" | sed 's%\.tar\.gz%%g' | cut -d- -f1 | sort -n | uniq || true)
  master_versions=$(curl -s http://board.plugandwork.net/${board_subfolder}/master/ | grep -v "Index" | grep "${artifact_name}" | cut -d '>' -f2 | cut -d '<' -f1 | sed "s%${artifact_name}-%%g" | sed 's%\.tar\.gz%%g' | cut -d- -f1 | sort -n | uniq || true)

  echo "Available versions for dev :"
  if [[ ! -z ${dev_versions} ]]; then
    for dev_version in ${dev_versions}; do
      echo "  - ${dev_version}"
    done
  else
    echo -e "No dev version available"
  fi

  echo -e ""
  echo "Available versions for staging :"
  if [[ ! -z ${staging_versions} ]]; then
    for staging_version in ${staging_versions}; do
      echo "  - ${staging_version}"
    done
  else
    echo -e "No staging version available"
  fi

  echo -e ""
  echo "Available versions for master :"
  if [[ ! -z ${master_versions} ]]; then
    for master_version in ${master_versions}; do
      echo "  - ${master_version}"
    done
  else
    echo -e "No master version available"
  fi
}  # list_available_versions()


containsElement () {
  local e
  for e in "${@:2}"; do [[ "$e" == "$1" ]] && return 0; done
  return 1
}


check_valid_version() {
  if [[ ! -z ${package} ]]; then
    board_subfolder=${package}
    artifact_name=${package}
  else
    board_subfolder=core
    artifact_name=plugandwork-core
  fi

  if [[ -n "${customer}" ]]; then
    board_subfolder=${board_subfolder}${customer_dash}
    artifact_name=${artifact_name}${customer_dash}
  fi

  raw_valid_versions=$(curl -s http://board.plugandwork.net/${board_subfolder}/${branch}/ | grep -v "Index" | grep "${artifact_name}" | cut -d '>' -f2 | cut -d '<' -f1 | sed "s%${artifact_name}-%%g" | sed 's%\.tar\.gz%%g' | cut -d- -f1 | sort -n | uniq)

  valid_versions=( $raw_valid_versions )

  if [[ -z ${artifact_version} ]]; then
    artifact_version="latest"
  fi

  if ! containsElement "${artifact_version}" "${valid_versions[@]}" ; then
    >&2 echo -e "--> Version ${artifact_version} is invalid. <--\n"
    list_available_versions >&2
    die "invalid version: ${artifact_version}"
  fi
}  # check_valid_version()


remove_rollback() {
  trap - INT TERM ERR QUIT STOP
  # function_exists 'on_exit' && on_exit  # necessary only if EXIT is catched

  # Clean rollback link
  if test -e "${deployment_path}"/current.in_case_of_rollback -o -L "${deployment_path}"/current.in_case_of_rollback ; then
    # -r because it may be a directory
    rm -rf "${deployment_path}"/current.in_case_of_rollback 2>/dev/null || true
  fi
}  # remove_rollback()


# A non-destructive exit for when the script exits naturally.
safe_exit() {
  remove_rollback
  exit 0
}


# Unless force is used, confirm with user
confirm() {
  (($force)) && return 0;

  read -p "$1 [y/N] " -n 1;
  [[ $REPLY =~ ^[Yy]$ ]];
}  # confitm()


is_webapp_group_rvm() {
  # WARNING: when given a user as parameter, these commands will return a newly added group, even if the user has not logged again and not effectively part of group yet
  # groups webapp | grep -q '\brvm\b'  # 'grep -qw rvm' works the same
  id -nG webapp | grep -qw rvm
}


am_i_group_rvm() {
  # groups | grep -q '\brvm\b'  # 'grep -qw rvm' works the same
  id -nG | grep -qw rvm
}


am_i_group_rvm_check() {
  if ! am_i_group_rvm ; then
    remove_rollback
    die "STOP!\n'webapp' user is not member of group 'rvm'.\nPlease connect as user 'root' and run the following command before. Afterwards, you will also need to login again as 'webapp' for the changes to appear.\n\n    usermod -a -G rvm webapp\n"
  fi
}


auto_update() {
  # even if run as 'root', wget and curl will not modify the current scripts ownership

  # update auxiliary scripts
  # but do not fail in case of old system installation where file permissions do not allow
  wget -q -N -T 30 -P "${SCRIPTS_DIR}" http://board.plugandwork.net/agent/paw_{symlinks,set_config} || true

  # Rely on server md5 for paw_agent, in case its modified date was modified locally
  CURRENT_MD5=$(md5sum ${SCRIPTS_DIR}/paw_agent | cut -d " " -f1 )
  SERVER_MD5=$(curl -s http://board.plugandwork.net/agent/paw_agent.md5 | grep ' paw_agent' | cut -d ' ' -f1 )
  if [[ "$CURRENT_MD5" = "$SERVER_MD5" ]]; then
    echo -e "${SCRIPTS_DIR}/paw_agent script is up to date ! \n"
  else
    echo -e "Found new version of paw_agent available ! Auto-updating..."
    curl -s http://board.plugandwork.net/agent/paw_agent -o "${SCRIPTS_DIR}/paw_agent"
    echo -e "Update completed ! \n"
    echo -e "Relaunching script with newer version..."

    # # Note: `exec` will change the process group ID
    # test -e /var/tmp/paw_agent.pgrp && rm /var/tmp/paw_agent.pgrp
    # exec ${SCRIPTS_DIR}/paw_agent $initial_args
    ${SCRIPTS_DIR}/paw_agent $initial_args
    safe_exit
  fi
}  # auto_update()


install_ruby() {
  test -e "${deployment_path}"/current/.ruby-version && ruby_version=`head -1 "${deployment_path}"/current/.ruby-version`

  # do not use "grep -q" https://stackoverflow.com/a/19120674/92471
  grep -i '^rvm_silence_path_mismatch_check_flag' ~/.rvmrc 1>/dev/null 2>&1 || echo -e "\nrvm_silence_path_mismatch_check_flag=1" >> ~/.rvmrc || true

  # Exit if no Ruby version requested. Should not happen since there is a default
  test -z "${ruby_version}" && return 0 || true

  # do not use "grep -q" https://stackoverflow.com/a/19120674/92471
  /usr/local/rvm/bin/rvm list | grep -i ' ruby-'"${ruby_version}" 1>/dev/null 2>&1 && return 0 || true

  /usr/local/rvm/bin/rvm install $ruby_version -C --with-jemalloc
  rval=$?

  source /etc/profile.d/rvm.sh
  rvm use $ruby_version

  /usr/local/rvm/bin/rvm gemset delete --force $ruby_version@global

  # /usr/local/rvm/wrappers/ruby-${ruby_version}/gem install --no-document bundler --version ${bundler_version}

  /usr/local/rvm/wrappers/ruby-${ruby_version}/gem install --no-document mongo colorize

  # https://stackoverflow.com/questions/54761120/rails-how-to-change-bundler-default-version

  # https://stackoverflow.com/questions/57306611/rails-how-can-i-remove-default-version-of-bundler/59636874#59636874

  # see also what is done in paw_build's Dockerfile
  # /usr/local/rvm/wrappers/ruby-${ruby_version}/gem list bundler | grep '^ *bundler' | ruby -ne '$_.scan(/(?<v>\d+(\.\d+)*)/).each {|v| puts v }' | grep -v "^${bundler_version}\$" | xargs -r -n1 /usr/local/rvm/wrappers/ruby-${ruby_version}/gem uninstall bundler --executables --version || true

  # find /usr/local/rvm/rubies/ruby-${ruby_version}/lib/ruby/gems -name bundler-2.1.4.gemspec

  # /usr/local/rvm/bin/rvm list

  # /usr/local/rvm/wrappers/ruby-${ruby_version}/gem list

  return $rval
}  # install_ruby()


cleanup_ruby() {
  /usr/local/rvm/bin/rvm cleanup all || true
}


default_ruby() {
  /usr/local/rvm/bin/rvm alias create default $ruby_version
  # /usr/local/rvm/bin/rvm use $ruby_version --default
}


install_nvm_nodejs() {
  test -e "${deployment_path}"/current/.nodejs-version && nodejs_version=`head -1 "${deployment_path}"/current/.nodejs-version`

  # Install NVM
  if test ! -d ~/.nvm ; then
    # script_nvm=`mktemp`
    # curl -s http://board.plugandwork.net/agent/install_nvm.sh -o "${script_nvm}"
    curl -o- http://board.plugandwork.net/agent/install_nvm.sh | bash
  fi

  # Ensure Yarn is always installed
  test -e ~/.nvm/default-packages || echo yarn >>~/.nvm/default-packages
  grep -zPq "^yarn\s" ~/.nvm/default-packages || echo yarn >>~/.nvm/default-packages

  # Load NVM
  test -z "${NVM_DIR}" && export NVM_DIR="$HOME/.nvm" || true
  test "$(type -t nvm)" != "function" -a -s "$NVM_DIR/nvm.sh" && \. "$NVM_DIR/nvm.sh" || true  # This loads nvm

  # Exit if no Nodejs version requested. Should not happen since there is a default
  test -z "${nodejs_version}" && return 0 || true

  nvm ls --no-colors --no-alias | grep -zPq "v${nodejs_version}\s" && rval=$? || rval=$?
  if test $rval -ne 0 ; then
    nvm install -b --no-progress --default v${nodejs_version} && rval=$? || rval=$?
  fi

  # Load requested Nodejs if not current
  test "$(nvm current)" != "v${nodejs_version}" && nvm use --silent "v${nodejs_version}" || true

  return $rval
}  # install_nvm_nodejs()


# Function to check if a string is a valid integer.integer.integer
# returns 0 for true as for any shell command
is_version() {
  re='^[0-9]+\.[0-9]+\.[0-9]+$'
  if [[ $1 =~ $re ]]; then
    return 0
  else
    return 1
  fi
}  # is_version()


create_indexes() {
    cd ${deployment_path}/current
    echo -e "Creating Mongo indexes..."
    RAILS_ENV=production bundle exec rake db:mongoid:create_indexes
    echo -e "Creating indexes completed !"
    safe_exit
}  # create_indexes()


deploy_package() {
  check_valid_version

  echo "Installing Plugandwork package ${package}${customer_dash} version ${artifact_version} from $branch repository..."
  echo -e "Deployment path is ${deployment_path_packages}"
  echo -e ""
  mkdir -p ${deployment_path_packages}
  cd ${deployment_path_packages}

  echo -e "Downloading package from repository..."
  curl http://board.plugandwork.net/${package}${customer_dash}/${branch}/${package}${customer_dash}-${artifact_version}.tar.gz -o ${package}-$artifact_version.tar.gz
  echo -e "Download complete !"
  echo -e ""

  echo -e "Removing potential old ${deployment_path_packages}/${package} folder"
  rm -rf ${deployment_path_packages}/${package}${customer_dash}

  echo -e "Unzipping archive..."
  tar xf ${package}${customer_dash}-${artifact_version}.tar.gz
  rm ${package}${customer_dash}-${artifact_version}.tar.gz
  echo -e "Unzipping completed !"

  # Find latest folder created (when using latest builds, we don't know the
  # name of the unzipped folder in advance)
  #
  # AF: the find below can return more than one result
  # folder_created=$(find . -maxdepth 1 -type d -cmin -1 | grep -v "current" | grep "$package${customer_dash}" | sed 's%\./%%g')
  folder_created=$(find . -maxdepth 1 -type d -cmin -1 -name "*${package}${customer_dash}*" ! -name current -exec ls -1td --time=status {} + | sed 's%^\./%%' | head -1)
  test -n "$folder_created" || rollback ${LINENO} $?

  echo -e "Renaming newly deployed package${customer_dash}"
  mv $folder_created $package${customer_dash}

  echo -e ""
  echo -e "Package $package${customer_dash} deployment completed !"
  echo -e ""

  echo -e "Add symlinks for potential packages..."
  /opt/plugandwork/bin/paw_symlinks
  echo -e "Add symlinks for packages done !"
  echo -e ""

  if test "${want_precompile}" -eq 1 ; then
    echo -e "Precompiling assets... "
    pushd "${deployment_path}"/current/ >/dev/null 2>&1
    RAILS_ENV=production bundle exec rake assets:precompile
    popd >/dev/null 2>&1

    echo -e "Restarting paw..."
    if test -e /opt/plugandwork/bin/paw_restart; then
      /opt/plugandwork/bin/paw_restart
    else
      sudo /opt/scripts/bin/restart_paw  # compatibility, older versions run as root and restarts Supervisord services
    fi
  fi

  safe_exit
}


run() {
  echo -n
  echo "       _                             _                    _
 _ __ | |_   _  __ _  __ _ _ __   __| |_      _____  _ __| | __
|  _ \| | | | |/ _  |/ _  |  _ \ / _  \ \ /\ / / _ \|  __| |/ /
| |_) | | |_| | (_| | (_| | | | | (_| |\ V  V / (_) | |  |   <
|  __/|_|\____|\___ |\____|_| |_|\____| \_/\_/ \___/|_|  |_|\_\\
|_|            |___/
      "

  if [ "${list_versions}" = 1 ]; then
    list_available_versions
    safe_exit
  fi

  # DO NOT PUT THAT AT THE BEGINNING OF THE SCRIPT AS USUAL, BECAUSE THE RECURSION IN THIS SCRIPT WOULD WAIT ITS OWN LOCK !
  # Avoid concurrent runs if run on a puppetized server
  if test -f /opt/scripts/inc/some_functions.bash ; then
    # The usual "avoid concurrent run" functions
    source /opt/scripts/inc/some_functions.bash
    pgrpfile=/var/tmp/paw_agent.pgrp
    check_if_running 3600
    add_on_exit check_if_running__clean
  fi

  if [ ${create_indexes} = 1 ]; then
    create_indexes
  fi

  if [[ ! -z ${package} ]]; then
    deploy_package
  fi

  check_valid_version

  main_version=`echo $artifact_version | cut -f1 -d.`

  if test "${main_version}" == 'latest' ; then
    am_i_group_rvm_check
  elif test $main_version -ge 4 ; then
    am_i_group_rvm_check
  elif test $update_ruby -ge 1 ; then
    am_i_group_rvm_check
  fi

  echo "Installing Plugandwork core${customer_dash} version $artifact_version from $branch repository..."
  echo -e "Deployment path is ${deployment_path}"
  echo -e ""
  mkdir -p "${deployment_path}"
  cd "${deployment_path}"

  echo -e "Downloading archive from repository..."
  curl http://board.plugandwork.net/core${customer_dash}/$branch/plugandwork-core${customer_dash}-${artifact_version}${dist_string}.tar.gz -o plugandwork-core${customer_dash}-${artifact_version}.tar.gz
  echo -e "Download complete !"
  echo -e ""

  echo -e "Unzipping archive..."
  tar xfz plugandwork-core${customer_dash}-${artifact_version}.tar.gz
  rm plugandwork-core${customer_dash}-${artifact_version}.tar.gz
  echo -e "Unzipping complete !"

  # Find latest folder created (when using latest builds, we don't know the
  # name of the unzipped folder in advance)
  #
  # AF: the find below can return more than one result
  #folder_created=$(find . -maxdepth 1 -type d -cmin -1 | grep -v "current" | grep "core" | sed 's%\./%%g')
  folder_created=$(find . -maxdepth 1 -type d -cmin -1 -name "*core${customer_dash}*" ! -name current -exec ls -1td --time=status {} + | sed 's%^\./%%' | head -1)
  test -n "$folder_created" || rollback ${LINENO} $?
  echo "Folder created: $folder_created"

  # Clean rollback link/dir if one already exists (!?)
  if test -e "${deployment_path}"/current.in_case_of_rollback -o -L "${deployment_path}"/current.in_case_of_rollback ; then
    # -r because it may be a directory
    rm -rf "${deployment_path}"/current.in_case_of_rollback 2>/dev/null || true
  fi

  # Prepare a rollback
  if test -e "${deployment_path}"/current -o -L "${deployment_path}"/current ; then
    # In case of future rollback
    if test -L "${deployment_path}"/current ; then
      # will preserve inode number when rolling back
      ln "${deployment_path}"/current "${deployment_path}"/current.in_case_of_rollback
    else
      # would work with either symlink or directory, but would not preserve inode number
      cp -a "${deployment_path}"/current "${deployment_path}"/current.in_case_of_rollback
    fi
  fi

  # Create/update "current" symlink
  # Archive previous "current" symlink if existing as a directory
  if [ ${keep_build_name} = 1 ]; then
    echo "Updating ${deployment_path}/current symlink"
    if [ -L "${deployment_path}/current" ]; then
      unlink "${deployment_path}/current"
    fi
    if [ -d "${deployment_path}/current" ]; then
      date=$(date '+%Y%m%d-%H%M%S')
      echo "Found an existing ${deployment_path} folder. Archiving it to current.${date}"
      mv "${deployment_path}/current" "${deployment_path}/current.${date}"
    fi
    ln -s "${deployment_path}/${folder_created}" "${deployment_path}/current"

  else
    if [ -d "${deployment_path}/current" ]; then
      date=$(date '+%Y%m%d-%H%M%S')
      echo "Found an existing ${deployment_path} folder. Archiving it to current.${date}"
      mv "${deployment_path}/current" "${deployment_path}/current.${date}"
    fi
    mv "${deployment_path}/${folder_created}" "${deployment_path}/current"
  fi
  echo -e "Updated current symlink !"

  # if latest and not wanting a download only (wanting an install) or force want an update of Ruby
  if test "${main_version}" == 'latest' -a \( $download_only -lt 1 -o $update_ruby -ge 1 \) ; then
    install_ruby
  # if version >=4 and not wanting a download only (wanting an install) or force want an update of Ruby
  elif test $main_version -ge 4 -a \( $download_only -lt 1 -o $update_ruby -ge 1 \) ; then
    install_ruby
  # otherwise if force want an update of Ruby
  elif test $update_ruby -ge 1 ; then
    install_ruby
  fi

  # if latest and not wanting a download only (wanting an install) or force want NVM managed Nodejs
  if test "${main_version}" == 'latest' -a \( $download_only -lt 1 -o $update_nodejs -ge 1 \) ; then
    install_nvm_nodejs
  # if version >=6 and not wanting a download only (wanting an install) or force want NVM managed Nodejs
  elif test $main_version -ge 6 -a \( $download_only -lt 1 -o $update_nodejs -ge 1 \) ; then
    install_nvm_nodejs
  # otherwise if force want NVM managed Nodejs
  elif test $update_nodejs -ge 1 ; then
    install_nvm_nodejs
  fi

  if [ -e "${deployment_path}"/current -a -x "/opt/plugandwork/bin/paw_set_config" ]; then
      echo -e "Set infrastructure dependent configuration..."
      echo -e "Calling /opt/plugandwork/bin/paw_set_config --app_path '${deployment_path}/current'"
      /opt/plugandwork/bin/paw_set_config --app_path "${deployment_path}/current"
      echo -e "Configurations set !"
  fi

  if [ ! ${deployment_path} = "/opt/plugandwork/apps/rce" ]; then
    # The path is custom. Do not update symlinks with `paw_symlinks` nor restart app.
    safe_exit
  fi

  # Force symlinks since current was replaced, it does not matter to check for open files
  if [ -x "/opt/plugandwork/bin/paw_symlinks" ]; then
      echo -e "Fixing symlinks and permissions..."
      echo -e "Calling /opt/plugandwork/bin/paw_symlinks..."
      /opt/plugandwork/bin/paw_symlinks --force
      echo -e "Fixing permissions complete !"
  fi

  (
    # subshell, because:
    # if there are only a few versions, the '|head' returns no line (nor endofline) and the 'read file_to_delete' exits with code 1
    # but since there is 'set -e' and 'trap ERR', this triggers and the script fails
    # ( doing a 'read variable || true' would have worked if there was not the 'while' just before which makes it infinite loop )
    #
    # moreover 'set -o pipefail' also has incidence with the while: https://stackoverflow.com/questions/11231937/bash-ignoring-error-for-a-particular-command/11231972#comment14754816_11231970
    #
    # so the workaround is to run in subshell disabling 'errexit' and untrap ERR.
    # once the subshell returns the flag and trap is restored.
    # and i made sure that the subshell has a 'true' command at the end so that it never appears to have failed to the parent caller

    set +e
    trap - ERR

    # Smart cleaning older versions - keep only the 3 most recent versions, avoiding current and rollback
    current_target=$(basename "$(readlink "${deployment_path}"/current || echo match_nothing)")
    current_rollback_target=$(basename "$(readlink "${deployment_path}"/current.in_case_of_rollback || echo match_nothing)")
    # will handle spaces in paths instead of a find ... | bla bla | xargs rm -rf
    # and -print0 would not work because of the bla bla commands piped between find and xargs
    find "${deployment_path}" -maxdepth 1 -name "plugandwork-core${customer_dash}*" | grep -v "${current_rollback_target}" | grep -v "${current_target}" | sort | head -n -3 | while read file_to_delete; do
      rm -rf "${file_to_delete}"
    done

    true
  )

  # if latest and not wanting a download only (wanting an install) or force want an update of Ruby
  if test "${main_version}" == 'latest' -a \( $download_only -lt 1 -o $update_ruby -ge 1 \) ; then
    default_ruby
    source /etc/profile.d/rvm.sh
    rvm use $ruby_version
  # if version >=4 and not wanting a download only (wanting an install) or force want an update of Ruby
  elif test $main_version -ge 4 -a \( $download_only -lt 1 -o $update_ruby -ge 1 \) ; then
    default_ruby
    source /etc/profile.d/rvm.sh
    rvm use $ruby_version
  # otherwise if force want an update of Ruby
  elif test $update_ruby -ge 1 ; then
    default_ruby
    source /etc/profile.d/rvm.sh
    rvm use $ruby_version
  fi

  if test $download_only -ge 1 ; then
    safe_exit
  fi

  echo -e "Restarting paw..."
  if test -e /opt/plugandwork/bin/paw_restart; then
    /opt/plugandwork/bin/paw_restart
  else
    sudo /opt/scripts/bin/restart_paw  # compatibility, older versions run as root and restarts Supervisord services
  fi

  echo -e "Resuming autoremediation..."
  # # Check if auto-remediation is disabled
  # # because non-root users cannot query custom facts, use `--external-dir`
  # autoremediate_disabled=`/opt/puppetlabs/bin/facter --external-dir=/etc/puppetlabs/facter/facts.d autoremediate.disabled`  # 'true' or 'false' (or empty or something else, in that case use it as 'false')
  sudo /opt/scripts/bin/resume_autoremediation

  echo -e ""
  echo -e "Deployment complete !"
  echo -e ""

}  # run()


# https://unix.stackexchange.com/a/495400/220216
debug() {
  local f=${FUNCNAME[1]} d=${#FUNCNAME[@]} c=$BASH_COMMAND
  if [ "$NOTRACE" ]; then
    case $debug_skip in ''|$d) debug_skip=;; *) return;; esac
    eval "case \$c in $NOTRACE) debug_skip=\$d; return; esac"
  fi

  # before the 1st command in a function XXX
  case $c in $f|"$f "*) return;; esac

  printf >&2 "%*s(%s) %s\n" $((d * 2 - 4)) "" "$f" "$c"
  #if test `echo "$c" | grep -q '\$'`; then
    # Very unsane and dangerous to eval unknown stuff
    # printf >&2 "%*s(%s)     => %s\n" $((d * 2 - 4)) ""  "$f" "`eval \"echo $c\"`"
  #fi
}  # debug()



###
#   PROGRAM STARTS HERE
###

auto_update

if test -e /etc/os-release ; then
  dist_string="-$(source /etc/os-release && echo ${ID}${VERSION_ID} | tr '[:upper:]' '[:lower:]' | cut -f1 -d.)"
elif test -x /usr/bin/lsb_release ; then
  dist_string="-$(lsb_release -si | tr '[:upper:]' '[:lower:]')$(lsb_release -sr | cut -f1 -d.)"
else
  dist_string=''
fi

# Defaults to Rocky 8
test -z "$dist_string" && dist_string='-rocky8'

# Centos 8 is not built anymore
echo "$dist_string" | grep -q '^-centos8' && dist_string='-rocky8'
echo "$dist_string" | grep -q '^-centos9' && dist_string='-rocky9'

# Iterate over options breaking -ab into -a -b when needed and --foo=bar into
# --foo bar
optstring=h
unset options
while (($#)); do
  case $1 in
    # If option is of type -ab
    -[!-]?*)
      # Loop over each character starting with the second
      for ((i=1; i < ${#1}; i++)); do
        c=${1:i:1}

        # Add current char to options
        options+=("-$c")

        # If option takes a required argument, and it's not the last char make
        # the rest of the string its argument
        if [[ $optstring = *"$c:"* && ${1:i+1} ]]; then
          options+=("${1:i+1}")
          break
        fi
      done
      ;;
    # If option is of type --foo=bar
    --?*=*) options+=("${1%%=*}" "${1#*=}") ;;
    # add --endopts for --
    --) options+=(--endopts) ;;
    # Otherwise, nothing special
    *) options+=("$1") ;;
  esac
  shift
done
set -- "${options[@]}"
unset options

# Print help if no arguments were passed.
[[ $# -eq 0 ]] && set -- "--help"

# Read the options and set stuff
while [[ $1 = -?* ]]; do
  case $1 in
    -h|--help) usage >&2; safe_exit ;;
    -l|--list-versions) list_versions=1 ;;
    --latest) artifact_version="latest" ;;
    --dev) branch="dev" ;;
    --staging) branch="staging" ;;
    --create-indexes) create_indexes=1 ;;
    --cleanup-ruby) cleanup_ruby=1 ;;
    --update-ruby) update_ruby=1 ;;
    --update-nodejs) update_nodejs=1 ;;
    --download-only) download_only=1 ;;
    --no-precompile) want_precompile=0 ;;
    --noprecompile) want_precompile=0 ;;
    # Deprecated flag. Do nothing, as some puppet code might still use it
    --keep-build-name) ;;
    -v|--version) shift; artifact_version=$1 ;;
    -c|--customer) shift; customer=$1 ;;
    -p|--path) shift; deployment_path=$1 ;;
    --shared_path) shift; shared_path=$1 ;;
    --package) shift; package=$1 ;;
    --debug) want_debug=1 ;;
    --init) run_init=1 ;;
    *) die "invalid option: $1" ;;
  esac
  shift
done

customer_dash=''
test -n "${customer}" && customer_dash="-${customer}"

# Store the remaining part as arguments.
args+=("$@")

if ! is_webapp_group_rvm && test `id -un` = root; then
  usermod -a -G rvm webapp
fi

# make sure this is created in advance since user webapp can not create dirs in /opt/plugandwork/shared on older installations
if test `id -un` = root; then
  mkdir -p "${shared_path}"/public_front-save
  chown webapp:webapp "${shared_path}"/public_front-save
fi

# Run paw_init before switching to non-root user
if test $run_init -eq 1 ; then
  # file is small, do not bother comparing md5sum
  curl -s http://board.plugandwork.net/agent/paw_init -o ${SCRIPTS_DIR}/paw_init || true

  exec ${SCRIPTS_DIR}/paw_init
  safe_exit  # probably unnecessary since there was an exec
fi

if test `id -u --name` != 'webapp' ; then
    # Note: `exec` will change the process group ID
    test -e /var/tmp/paw_agent.pgrp && rm /var/tmp/paw_agent.pgrp  # avoid warning
    exec su --login webapp ${SCRIPTS_DIR}/paw_agent -- $initial_args  # since centos8, if not using --login, an umask of 077 is applied !
    safe_exit  # probably unnecessary since there was an exec
fi


if [ ${want_debug} = 1 ]; then
  # NOTRACE=''
  set -o functrace

  # Unfortunately the debug() function will show variables used in command lines, so default to `set -x`
  # shopt -s extdebug
  # trap debug DEBUG
  set -x
fi

# Set our rollback function for unexpected exits.
trap 'rollback ${LINENO} $?' INT TERM ERR QUIT STOP

run

remove_rollback

test "${cleanup_ruby}" -eq 1 && cleanup_ruby

# This has to be run last not to rollback changes we've made.
safe_exit
