#!/bin/bash
#
# Startup script for Artifactory in Tomcat Servlet Engine
#

ARTIFACTORY_NAME=artifactory
METADATA_NAME=metadata
ACCESS_NAME=access
ROUTER_NAME=router
ROUTER_SERVICE_NAME=router_service
FRONTEND_NAME=frontend
JFCONNECT_NAME=jfconnect
JFCONNECT_SERVICE_NAME=jfconnect_service
OBSERVABILITY_NAME=observability
EVENT_NAME=event
FEDERATION_NAME=federation    
EVIDENCE_NAME=evidence

# Map contains environment variable key from old version (<7.x) to key from 7.x version
# This info will be used to assign value from old key to new key
# Note : Allowed values in a environment key are upper case alphabet, number and underscore
ART_ENV_MAP="""
                shared.node.primary=HA_IS_PRIMARY
                shared.node.id=HA_NODE_ID
                shared.node.ip=HA_HOST_IP
                artifactory.node.membershipPort=HA_MEMBERSHIP_PORT
                artifactory.node.haDataDir=HA_DATA_DIR
                artifactory.node.haBackupDir=HA_BACKUP_DIR
                artifactory.port=SERVER_XML_ARTIFACTORY_PORT
                artifactory.tomcat.connector.maxThreads=SERVER_XML_ARTIFACTORY_MAX_THREADS
                access.tomcat.connector.maxThreads=SERVER_XML_ACCESS_MAX_THREADS
                artifactory.tomcat.connector.extraConfig=SERVER_XML_ARTIFACTORY_EXTRA_CONFIG
                access.tomcat.connector.extraConfig=SERVER_XML_ACCESS_EXTRA_CONFIG
                shared.tomcat.connector.extra=SERVER_XML_EXTRA_CONNECTOR
                shared.extraJavaOpts=EXTRA_JAVA_OPTS
                shared.extraJavaOpts=EXTRA_JAVA_OPTIONS
                artifactory.ha.waitForMasterKey=HA_WAIT_FOR_MASTER_KEY
                artifactory.extraConf=ARTIFACTORY_EXTRA_CONF
                access.extraConf=ACCESS_EXTRA_CONF
                shared.database.type=DB_TYPE
                shared.database.host=DB_HOST
                shared.database.port=DB_PORT
                shared.database.url=DB_URL
                shared.database.user=DB_USER
                shared.database.password=DB_PASSWORD
                artifactory.database.maxOpenConnections=DB_POOL_MAX_ACTIVE
                artifactory.database.maxIdleConnections=DB_POOL_MAX_IDLE
                access.database.maxOpenConnections=DB_POOL_MAX_ACTIVE
                access.database.maxIdleConnections=DB_POOL_MAX_IDLE
            """

createDirectory () {
    [[ -d "$1" ]] || mkdir -p $1 || errorExit "Could not create dir $1"
}

errorArtHome() {
    echo
    echo -e "\033[31m** $1\033[0m"
    echo
    exit 1
}

checkArtHome() {
    if [ -z "${JF_PRODUCT_HOME}/app" ] || [ ! -d "${JF_PRODUCT_HOME}/app" ]; then
        errorArtHome "ERROR: Artifactory home folder not defined or does not exists at ${JF_PRODUCT_HOME}/app"
    fi
}

checkTomcatHome() {
    if [ -z "$TOMCAT_HOME" ] || [ ! -d "$TOMCAT_HOME" ]; then
        errorArtHome "ERROR: Tomcat Artifactory folder not defined or does not exists at $TOMCAT_HOME"
    fi
    export CATALINA_HOME="$TOMCAT_HOME"
}

# Do the actual permission check and chown
checkOwner () {
    local file_to_check=$1
    # get current user id an group id
    local user_id_to_check=$(id -u)
    local group_id_to_check=$(id -g)

    logger "Checking permissions on $file_to_check"
    local stat=( $(stat -Lc "%u %g" ${file_to_check}) )
    local user_id=${stat[0]}
    local group_id=${stat[1]}

    if [[ "${user_id}" != "${user_id_to_check}" ]] || [[ "${group_id}" != "${group_id_to_check}" ]] ; then
        errorArtHome "${file_to_check} is not owned by ${user_to_check}"
    else
        logger "$file_to_check is already owned by $user_id_to_check:$group_id_to_check."
    fi
}

createSymlink() {
    local target=$1
    local link=$2

    [ ! -z "${target}" ] || errorArtHome "target is not provided to create a symlink"
    [ ! -z "${link}"   ] || errorArtHome "source is not provided to create a symlink"

    if [[ -d "${link}" && ! -L "${link}" ]];
    then
        # Uncomment this to get debug logs
        # if its a directory, create a copy and move its content back to symlink
        # logger """The target symbolic link ${link} is a directory,
        #                 performing the following operations to change this from directory to symlink,
        #                 - move ${link} to ${link}.copy
        #                 - create link ${link} to ${target}
        #                 - copy content of ${link}.copy to ${link}"""

        mv -f  "${link}"        "${link}.copy" || errorArtHome "Could not create ${link}.copy directory from ${link}"
        ln -s  "${target}"      "${link}"      || errorArtHome "Could not create link from ${link} to ${target}"
        cp -fr "${link}.copy"/* "${link}"      || errorArtHome "Could not copy content from ${link}.copy to ${link}"
        rm -fr "${link}".copy;
    elif [ ! -L "${link}" ];
    then
        ln -s "${target}" "${link}" || errorArtHome "Could not create link from ${link} to ${target}"
    fi
}

findShutdownPort() {
    if command -v netstat 2>/dev/null; then
        SHUTDOWN_PORT=$(netstat -vatn|grep LISTEN|grep $CATALINA_MGNT_PORT|wc -l)
    else
        (</dev/tcp/localhost/${CATALINA_MGNT_PORT}) 2>/dev/null
        if [[ $? -eq 0 ]]; then
            SHUTDOWN_PORT=1
        else
            SHUTDOWN_PORT=0
        fi
    fi
}

isAlive() {
    pidValue=""
    javaPs=""
    if [ -e "$JF_ARTIFACTORY_PID" ]; then
        pidValue=`cat $JF_ARTIFACTORY_PID`
        if [ -n "$pidValue" ]; then
            javaPs="`ps -p $pidValue | grep java`"
        fi
    fi
}

testPermissions () {
    # Create JF_PRODUCT_HOME/var directory - to handle zip installations
    if [ ! -L "${JF_PRODUCT_HOME}/var" ]; then
        createDirectory "${JF_PRODUCT_HOME}/var"
    fi
    testDirectoryPermissions "${JF_PRODUCT_HOME}/var"
}

# Set and configure DB type
setupHA () {
    local isHaEnabled=
    local transformedKey=

    getSystemValue "shared.node.haEnabled" "NOT_SET"
    isHaEnabled="${YAML_VALUE}"

    if [[ "$isHaEnabled" != "NOT_SET" ]]; then

        local haDataDirKey="artifactory.node.haDataDir"
        getSystemValue "${haDataDirKey}" "NOT_SET"
        local haDataDir="${YAML_VALUE}"

        if [[ "${haDataDir}" != "NOT_SET" ]]; then
            createDirectory "${haDataDir}"
            testDirectoryPermissions "${haDataDir}"
        fi

        local haBakupDirKey="artifactory.node.haBackupDir"
        getSystemValue "${haBakupDirKey}" "NOT_SET"
        local haBackupDir="${YAML_VALUE}"

        if [[ "${haBackupDir}" != "NOT_SET" ]]; then
            createDirectory "${haBackupDir}"
            testDirectoryPermissions "${haBackupDir}"
        fi

        # Install license file if exists in /tmp
        if ls /tmp/art*.lic 1> /dev/null 2>&1; then
            logger "Found /tmp/art*.lic. Using it..."
            cp -v /tmp/art*.lic $ART_ETC/artifactory.lic
        fi
    fi
}

# Generate an artifactory.config.import.yml if parameters passed
# Only if artifactory.config.import.yml does not already exist!
# RETAINED for backward compatability only
prepareArtConfigYaml () {
    local artifactory_config_import_yml=${ARTIFACTORY_DATA}/etc/artifactory.config.import.yml
    if [ ! -f ${artifactory_config_import_yml} ]; then
        if [ -n "$AUTO_GEN_REPOS" ] || [ -n "$ART_BASE_URL" ] || [ -n "$ART_LICENSE" ]; then

            # Make sure license is provided (must be passed in Pro)
            if [ -z "$ART_LICENSE" ]; then
                errorExit "To use the feature of auto configuration, you must pass a valid Artifactory license as an ART_LICENSE environment variable!"
            fi

            logger "Generating ${artifactory_config_import_yml}"
            [ -n "$ART_LICENSE" ] && LIC_STR="licenseKey: $ART_LICENSE"
            [ -n "$ART_BASE_URL" ] && BASE_URL_STR="baseUrl: $ART_BASE_URL"
            [ -n "$AUTO_GEN_REPOS" ] && GEN_REPOS_STR="repoTypes:"

            cat <<EY1 > "$artifactory_config_import_yml"
version: 1
GeneralConfiguration:
  ${LIC_STR}
  ${BASE_URL_STR}
EY1

            if [ -n "$GEN_REPOS_STR" ]; then
                cat <<EY2 >> "$artifactory_config_import_yml"
OnboardingConfiguration:
  ${GEN_REPOS_STR}
EY2
                for repo in $(echo ${AUTO_GEN_REPOS} | tr ',' ' '); do
                    cat <<EY3 >> "$artifactory_config_import_yml"
   - ${repo}
EY3
                done
            fi
        fi
    fi
}

# Set and configure DB type
setDBType () {
    logger "Checking DB_TYPE"
    # TODO Will this be supported ?
    # A decision has to be made on the design and flow for this
    # if [ -f "${ART_ETC}/.secrets/.temp.db.properties" ]
    # then
    #     SECRET_DB_PROPS_FILE="${ART_ETC}/.secrets/.temp.db.properties"
    #     logger "Detected secret db.properties file at ${SECRET_DB_PROPS_FILE}. Secret file will override default db.properties file as well as environment variables."
    #     DB_TYPE_FROM_SECRET=$(grep -E "(type).*" "$SECRET_DB_PROPS_FILE" | awk -F"=" '{ print $2 }')
    #     if [[ "$DB_TYPE_FROM_SECRET" =~ ^(postgresql|mysql|oracle|mssql|mariadb)$ ]]; then DB_TYPE=${DB_TYPE_FROM_SECRET} ; fi
    # fi
    # if [ ! -z "${DB_TYPE}" ] && [ "${DB_TYPE}" != derby ]; then
    #     logger "DB_TYPE is set to $DB_TYPE"
    #     DB_PROPS=${ART_ETC}/db.properties
    #     if [ ! -z "$SECRET_DB_PROPS_FILE" ]
    #     then
    #         DB_PROPS=${SECRET_DB_PROPS_FILE}
    #         logger "DB_PROPS set to: ${DB_PROPS}"
    #     fi
    # fi
}

startFederation() {
    local action=${@:-"start"}

    getSystemValue "${FEDERATION_NAME}.enabled" "true"
    local isEnabled="${YAML_VALUE}"
    [[ "${isEnabled}" == "false" ]] && return
    if runFederation; then
        chmod +x ${federationScript}
        . ${federationScript} "${action}"
    fi
}

stopFederation() {
    getSystemValue "${FEDERATION_NAME}.enabled" "true"
    local isEnabled="${YAML_VALUE}"
    [[ "${isEnabled}" == "false" ]] && return
    if runFederation; then
        chmod +x ${federationScript}
        ${federationScript} stop
    fi
}

startEvidence() {
    local action=${@:-"start"}

    getSystemValue "${EVIDENCE_NAME}.enabled" "false"
    local isEnabled="${YAML_VALUE}"
    [[ "${isEnabled}" == "false" ]] && return
    if runEvidence; then
        chmod +x ${evidenceScript}
        . ${evidenceScript} "${action}"
    fi
}

stopEvidence() {
    if runEvidence; then
        chmod +x ${evidenceScript}
        ${evidenceScript} stop
    fi
}

stop() {
    # The default CATALINA_MGNT_PORT is 8015
    # TODO[by Amith]: should this come from system yaml
    CATALINA_MGNT_PORT=8015
    echo "Using the default catalina management port ($CATALINA_MGNT_PORT) to test shutdown"
    isAlive
    findShutdownPort
    if [ $SHUTDOWN_PORT -eq 0 ] && [ -z "$javaPs" ]; then
        echo "Artifactory Tomcat already stopped"
        RETVAL=0
    else
        echo "Stopping Artifactory Tomcat..."
        if [ $SHUTDOWN_PORT -ne 0 ]; then
            $TOMCAT_HOME/bin/shutdown.sh
            RETVAL=$?
        else
            RETVAL=1
        fi

        hardKillTomcat

        if [ $SHUTDOWN_PORT -eq 0 ] && [ -z "$javaPs" ]; then
           echo "Artifactory Tomcat stopped"
        else
           echo "ERROR: Artifactory Tomcat did not stop"
           RETVAL=1
        fi
    fi
    [ $RETVAL=0 ] && rm -f "$JF_ARTIFACTORY_PID"
    
    # This script is called when artifactory is started as  docker container and when it is started in standalone mode
    local confFile="${JF_PRODUCT_HOME}/var/etc/logrotate/logrotate.conf"
    io_checkOwner "${confFile}" "$(id -un)" "$(id -gn)" "yes" && removeLogRotation "$JF_PRODUCT_HOME" "$(id -un)" || true
    
    stopFederation
    stopEvidence
    for serviceScript in ${MANDATORY_JF_SERVICES_SCRIPT}; do
        performActionOnScript "${serviceScript}" "stop"
    done
}

addConfFiles(){
    addExtraFiles "${ARTIFACTORY_NAME}.extraConf" "$ART_ETC"         "/artifactory_extra_conf"
    addExtraFiles "${ACCESS_NAME}.extraConf"      "$ACCESS_ETC"      "/access_extra_conf"
}

addLogRotation() {
    local logRotateFolder="${JF_PRODUCT_HOME}/var/etc/logrotate"
    if [ ! -d "$logRotateFolder" ]; then
        mkdir -p "$logRotateFolder"
    fi

    local confFile="$logRotateFolder/logrotate.conf"
    if [ ! -f "$confFile" ]; then
        touch "$confFile"
    fi

    # This script is called when artifactory is started as  docker container and when it is started in standalone mode

    io_checkOwner "${confFile}" "$(id -un)" "$(id -gn)" "yes" || { return 0; }
    configureLogRotation "artifactory" "${JF_PRODUCT_HOME}" "$(id -un)" "$(id -gn)" || true

}

startupActions () {
    checkJavaVersion
    checkULimits "${MIN_MAX_OPEN_FILES}" "${MIN_MAX_OPEN_PROCESSES}"
    validateSystemYaml
    validateDbconnection "${JF_PRODUCT_HOME}"
    testPermissions
    syncEtc
    setupNodeDetails
    setupHA
    addConfFiles
    addExtraJavaArgs
    prepareArtConfigYaml
    addLogRotation
    addCertsToJavaKeystore
    prepareTomcat
    prioritizeCustomJoinKey
    configureServerXml
    bootstrapJavaSecurityFile
    exportEnv "shared"
    exportEnv "${ARTIFACTORY_NAME}"
    if runAccessOnArtifactoryTomcat; then
        exportEnv "${ACCESS_NAME}"
        copyDerbyDataToAccess
    fi
    setRouterToplogy
    displayEnv
}

start() {
    export JF_ARTIFACTORY_PID=${JF_PRODUCT_HOME}/app/run/${ARTIFACTORY_NAME}.pid

    export CATALINA_PID="$JF_ARTIFACTORY_PID"
    [ -x $TOMCAT_HOME/bin/catalina.sh ] || chmod +x $TOMCAT_HOME/bin/*.sh
    #create ${JF_PRODUCT_HOME}/app/run
    if [ -n "$JF_ARTIFACTORY_PID" ];
    then
        mkdir -p $(dirname "$JF_ARTIFACTORY_PID") || \
        errorArtHome "Could not create dir for $JF_ARTIFACTORY_PID";
    fi
    if [ -z "$@" ];
    then
        startupActions

        for serviceScript in ${MANDATORY_JF_SERVICES_SCRIPT}; do
            local action="start"
            if [ "${serviceScript}" == "${accessScript}" ]; then
                action=""
            fi
            performActionOnScript "${serviceScript}" "${action}"
        done
        startFederation

        startEvidence
        #default to catalina.sh run
        setCatalinaOpts
        if $(isConsoleLogDisabled >/dev/null 2>&1); then
            $TOMCAT_HOME/bin/catalina.sh run
        else
            $TOMCAT_HOME/bin/catalina.sh run > >(tee >(redirectServiceLogsToFile)) 2>&1
        fi
    else
        if [ "$@" == "stop" ];
        then
            setCatalinaOpts
            stop
        else
            startupActions

            for serviceScript in ${MANDATORY_JF_SERVICES_SCRIPT}; do
                performActionOnScript "${serviceScript}" "start"
            done
            startFederation "start"
            startEvidence "start"
            setCatalinaOpts
            # Start tomcat
            $TOMCAT_HOME/bin/catalina.sh "$@"
        fi
    fi
}

check() {
    for serviceScript in ${MANDATORY_JF_SERVICES_SCRIPT}; do
        performActionOnScript "${serviceScript}" "status"
    done

    if [ -f $JF_ARTIFACTORY_PID ]; then
        echo "Artifactory is running, on pid="`cat $JF_ARTIFACTORY_PID`
        echo ""
        exit 0
    fi

    echo "Checking arguments to Artifactory: "
    echo "JF_PRODUCT_HOME    =  ${JF_PRODUCT_HOME}"
    echo "TOMCAT_HOME        =  $TOMCAT_HOME"
    echo "JF_ARTIFACTORY_PID =  $JF_ARTIFACTORY_PID"
    echo "JAVA_HOME          =  $JAVA_HOME"
    echo "JAVA_OPTIONS       =  $JAVA_OPTIONS"
    echo

    exit 1
}

setCatalinaOpts() {
    setupTomcatRedirection # This ensures catalina logs are redirected to the common log file
    export CATALINA_OPTS="$JAVA_OPTIONS -Djruby.bytecode.version=1.8"
}

sourceScript(){
    local fileName=$1

    [ ! -z "${fileName}" ] || errorExit "Target file is not set"
    [   -f "${fileName}" ] || errorExit "${fileName} file is not found"
    source "${fileName}"   || errorExit "Unable to source ${fileName}, please check if the $USER user has permissions to perform this action"
}

initHelpers(){
    local systemYamlHelper="${ARTIFACTORY_BIN_FOLDER}"/systemYamlHelper.sh
    local installerCommon="${ARTIFACTORY_BIN_FOLDER}"/installerCommon.sh
    local artCommon="${ARTIFACTORY_BIN_FOLDER}"/artifactoryCommon.sh

    export YQ_PATH="${ARTIFACTORY_BIN_FOLDER}/../third-party/yq"
    sourceScript "${systemYamlHelper}"

    sourceScript "${installerCommon}"
    sourceScript "${artCommon}"
    setupScriptLogsRedirection || true

    # init at each service startup
    export JF_SYSTEM_YAML="${JF_PRODUCT_HOME}/var/etc/system.yaml"
}

init() {
    initHelpers
    translateEnv "${ART_ENV_MAP}"
    initJava
    initNode
}

ARTIFACTORY_BIN_FOLDER="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

MIN_MAX_OPEN_FILES=32000
MIN_MAX_OPEN_PROCESSES=1024

accessScript=${ARTIFACTORY_BIN_FOLDER}/../access/bin/access.sh
metadataScript=${ARTIFACTORY_BIN_FOLDER}/../metadata/bin/metadata.sh
routerScript=${ARTIFACTORY_BIN_FOLDER}/../router/bin/router.sh
frontendScript=${ARTIFACTORY_BIN_FOLDER}/../frontend/bin/frontend.sh
eventScript=${ARTIFACTORY_BIN_FOLDER}/../event/bin/event.sh
jfconnectScript=${ARTIFACTORY_BIN_FOLDER}/../jfconnect/bin/jfconnect.sh
observabilityScript=${ARTIFACTORY_BIN_FOLDER}/../observability/bin/observability.sh
federationScript=${ARTIFACTORY_BIN_FOLDER}/../rtfs/bin/artifactory-federation.sh
evidenceScript=${ARTIFACTORY_BIN_FOLDER}/../evidence/bin/evidence.sh

# Any new mandatory service to be started or stopped can be added to this variable
MANDATORY_JF_SERVICES_SCRIPT="${routerScript} ${metadataScript} ${eventScript} ${frontendScript} ${observabilityScript}"

export JF_PRODUCT_HOME="$(cd ${ARTIFACTORY_BIN_FOLDER}/../.. && pwd)"
export JF_ARTIFACTORY_PID="${JF_PRODUCT_HOME}/app/run/artifactory.pid"

artDefaultFile="${ARTIFACTORY_BIN_FOLDER}/artifactory.default"

. ${artDefaultFile} || errorArtHome "ERROR: $artDefaultFile does not exist or not executable"
PRODUCT_ETC=${JF_PRODUCT_HOME}/var/etc
ART_ETC=$PRODUCT_ETC/$ARTIFACTORY_NAME
ACCESS_ETC=$PRODUCT_ETC/$ACCESS_NAME

init

if runAccess && ! runAccessOnArtifactoryTomcat; then
    MANDATORY_JF_SERVICES_SCRIPT="${accessScript} ${MANDATORY_JF_SERVICES_SCRIPT}"
fi

if runJFConnect; then
  MANDATORY_JF_SERVICES_SCRIPT="${MANDATORY_JF_SERVICES_SCRIPT} ${jfconnectScript}"
fi

if [ "x$1" = "xcheck" ]; then
    checkJavaVersion
    addExtraJavaArgs
    check
fi

# Extra termination steps needed
terminate () {
    echo "Caught termination signal"
    for serviceScript in ${MANDATORY_JF_SERVICES_SCRIPT}; do
        performActionOnScript "${serviceScript}" "stop"
    done
    stopFederation
    stopEvidence
}

# Catch Ctrl+C and other termination signals to try graceful shutdown
trap terminate SIGINT SIGTERM SIGHUP

checkArtHome
checkTomcatHome

usage() {
    cat << END_USAGE

./$0 - Script to manage ${ARTIFACTORY_NAME} services.

Usage: ./${0} <action>

action: start|stop|restart|status|check|run|help

Note : If no action is passed, the services will be run in foreground.
END_USAGE
}

# run application in the foreground if nothing is passed
if [[ $# == 0 ]]; then 
    start
    exit 0
fi

case "$@" in
  start|stop)
    start "$@" 
    ;;
  restart)
    start "stop"
    start "start"
    ;;
  status|check)
    check
    ;;
  help)
    usage
    ;;
  *)
    usage
    exit 1
esac

