#!/bin/bash
#
# Copyright (c) 2012-2020 Red Hat, Inc.
# This program and the accompanying materials are made
# available under the terms of the Eclipse Public License 2.0
# which is available at https://www.eclipse.org/legal/epl-2.0/
#
# SPDX-License-Identifier: EPL-2.0
#
# Contributors:
#   Red Hat, Inc. - initial API and implementation
#

init_global_variables () {
  # For coloring console output
  BLUE='\033[1;34m'
  GREEN='\033[0;32m'
  NC='\033[0m'

  USAGE="
Usage:
  che [COMMAND]
     start                              Starts server with output in the background
     stop                               Stops ${CHE_MINI_PRODUCT_NAME} server
     run                                Starts server with output in the foreground

Variables:
    CHE_SERVER_ACTION                   Another way to set the [COMMAND] to [run | start | stop]
    CHE_PORT                            The port the Che server will listen on
    CHE_IP                              The IP address of the host - must be set if remote clients connecting
    CHE_BLOCKING_ENTROPY                Starts Tomcat with blocking entropy: -Djava.security.egd=file:/dev/./urandom
    CHE_LAUNCH_DOCKER_REGISTRY          If true, uses Docker registry to save ws snapshots instead of disk
    CHE_REGISTRY_HOST                   Hostname of Docker registry to launch, otherwise 'localhost'
    CHE_LOG_LEVEL                       [INFO | DEBUG] Sets the output level of Tomcat messages
    CHE_DEBUG_SERVER                    If true, activates Tomcat's JPDA debugging mode
    CHE_DEBUG_SUSPEND                   If true, Tomcat will start suspended waiting for debugger
    CHE_HOME                            Where the Che assembly resides - self-determining if not set
"

  # Use blocking entropy -- needed for some servers
  DEFAULT_CHE_BLOCKING_ENTROPY=false
  CHE_BLOCKING_ENTROPY=${CHE_BLOCKING_ENTROPY:-${DEFAULT_CHE_BLOCKING_ENTROPY}}

  DEFAULT_CHE_SERVER_ACTION=run
  CHE_SERVER_ACTION=${CHE_SERVER_ACTION:-${DEFAULT_CHE_SERVER_ACTION}}

  DEFAULT_CHE_LAUNCH_DOCKER_REGISTRY=false
  CHE_LAUNCH_DOCKER_REGISTRY=${CHE_LAUNCH_DOCKER_REGISTRY:-${DEFAULT_CHE_LAUNCH_DOCKER_REGISTRY}}

  # Must be exported as this will be needed by Tomcat's JVM
  DEFAULT_CHE_REGISTRY_HOST=localhost
  export CHE_REGISTRY_HOST=${CHE_REGISTRY_HOST:-${DEFAULT_CHE_REGISTRY_HOST}}

  DEFAULT_CHE_PORT=8080
  export CHE_PORT=${CHE_PORT:-${DEFAULT_CHE_PORT}}

  DEFAULT_CHE_IP=
  CHE_IP=${CHE_IP:-${DEFAULT_CHE_IP}}

  DEFAULT_CHE_LOG_LEVEL=INFO
  CHE_LOG_LEVEL=${CHE_LOG_LEVEL:-${DEFAULT_CHE_LOG_LEVEL}}

  DEFAULT_CHE_LOGS_DIR="${CATALINA_HOME}/logs/"
  export CHE_LOGS_DIR=${CHE_LOGS_DIR:-${DEFAULT_CHE_LOGS_DIR}}

  DEFAULT_CHE_DEBUG_SERVER=false
  CHE_DEBUG_SERVER=${CHE_DEBUG_SERVER:-${DEFAULT_CHE_DEBUG_SERVER}}

  DEFAULT_CHE_DEBUG_SUSPEND="false"
  CHE_DEBUG_SUSPEND=${CHE_DEBUG_SUSPEND:-${DEFAULT_CHE_DEBUG_SUSPEND}}
}

error () {
  echo
  echo "!!!"
  echo -e "!!! ${1}"
  echo "!!!"
  return 0
}

usage () {
  echo "${USAGE}"
}

set_environment_variables () {
  ### Set value of derived environment variables.

  # CHE_DOCKER_IP is used internally by Che to set its IP address
  if [[ -z "${CHE_DOCKER_IP}" ]]; then
    if [[ -n "${CHE_IP}" ]]; then
        export CHE_DOCKER_IP="${CHE_IP}"
    fi
  fi

  # Convert Tomcat environment variables to POSIX format.
  if [[ "${JAVA_HOME}" == *":"* ]]; then
    JAVA_HOME=$(echo /"${JAVA_HOME}" | sed  's|\\|/|g' | sed 's|:||g')
  fi

  # Convert Che environment variables to POSIX format.
  if [[ "${CHE_HOME}" == *":"* ]]; then
    CHE_HOME=$(echo /"${CHE_HOME}" | sed  's|\\|/|g' | sed 's|:||g')
  fi

  # Sets the location of the application server and its executables
  # Internal property - should generally not be overridden
  export CATALINA_HOME="${CHE_HOME}/tomcat"

  # Convert windows path name to POSIX
  if [[ "${CATALINA_HOME}" == *":"* ]]; then
    CATALINA_HOME=$(echo /"${CATALINA_HOME}" | sed  's|\\|/|g' | sed 's|:||g')
  fi

  if [[ "${CHE_DEBUG_SUSPEND}" == "true" ]]; then
    export JPDA_SUSPEND="y"
  else
    export JPDA_SUSPEND="n"
  fi

  # Internal properties - should generally not be overridden
  export CATALINA_BASE="${CHE_HOME}/tomcat"
  export ASSEMBLY_BIN_DIR="${CATALINA_HOME}/bin"
  export CHE_LOGS_LEVEL="${CHE_LOG_LEVEL}"
}

docker_exec() {
  "$(which docker)" "$@"
}

start_che_server () {
 if ${CHE_LAUNCH_DOCKER_REGISTRY} ; then
    # Export the value of host here
    launch_docker_registry
  fi

  #########################################
  # Launch Che natively as a tomcat server
  call_catalina
}

stop_che_server () {
  CHE_SERVER_ACTION="stop"
  echo -e "Stopping Che server running on localhost:${CHE_PORT}"
  call_catalina >/dev/null 2>&1
}

call_catalina () {
  # Test to see that Che application server is where we expect it to be
  if [ ! -d "${ASSEMBLY_BIN_DIR}" ]; then
    error "Could not find Che's application server."
    return 1;
  fi

  ### Initialize default JVM arguments to run che
  if [[ "${CHE_BLOCKING_ENTROPY}" == true ]]; then
    [ -z "${JAVA_OPTS}" ] && JAVA_OPTS="-Xms256m -Xmx1024m"
  else
    [ -z "${JAVA_OPTS}" ] && JAVA_OPTS="-Xms256m -Xmx1024m -Djava.security.egd=file:/dev/./urandom"
  fi

  ### Cannot add this in setenv.sh.
  ### We do the port mapping here, and this gets inserted into server.xml when tomcat boots
  export JAVA_OPTS="${JAVA_OPTS} -Dport.http=${CHE_PORT} -Dche.home=${CHE_HOME}"
  export SERVER_PORT=${CHE_PORT}

  # Launch the Che application server, passing in command line parameters
  if [[ "${CHE_DEBUG_SERVER}" == true ]]; then
    "${ASSEMBLY_BIN_DIR}"/catalina.sh jpda ${CHE_SERVER_ACTION}
  else
    "${ASSEMBLY_BIN_DIR}"/catalina.sh ${CHE_SERVER_ACTION}
  fi
}

kill_and_launch_docker_registry () {
  echo -e "Launching Docker container named ${GREEN}registry${NC} from image ${GREEN}registry:2${NC}."
  docker_exec rm -f registry &> /dev/null || true
  docker_exec run -d -p 5000:5000 --restart=always --name registry registry:2
}

launch_docker_registry () {
    echo "Launching a Docker registry for workspace snapshots."
    CREATE_NEW_CONTAINER=false

    # Check to see if the registry docker was not properly shut down
    docker_exec inspect registry &> /dev/null || DOCKER_INSPECT_EXIT=$? || true
    if [ "${DOCKER_INSPECT_EXIT}" != "1" ]; then

      # Existing container running registry is found.  Let's start it.
      echo -e "Found a registry container named ${GREEN}registry${NC}. Attempting restart."
      docker_exec start registry &>/dev/null || DOCKER_EXIT=$? || true

      # Existing container found, but could not start it properly.
      if [ "${DOCKER_EXIT}" == "1" ]; then
        echo "Initial start of registry docker container failed... Attempting docker restart and exec."
        CREATE_NEW_CONTAINER=true
      fi

    echo "Successful restart of registry container."
    echo

    # No existing Che container found, we need to create a new one.
    else
      CREATE_NEW_CONTAINER=true
    fi

    if ${CREATE_NEW_CONTAINER} ; then
      # Container in bad state or not found, kill and launch new container.
      kill_and_launch_docker_registry
    fi
}

perform_database_migration() {
  CHE_DATA=/data
  if [ -f ${CHE_DATA}/db/che.mv.db ]; then
    echo "!!! Detected Che database, that is stored by an old path: ${CHE_DATA}/db/che.mv.db"
    echo "!!! In case if you want to use it, move it manually to the new path ${CHE_DATA}/storage/db/che.mv.db"
    echo "!!! It will be moved there automatically, if no database is present by the new path"
    if [ ! -f ${CHE_DATA}/storage/db/che.mv.db ]; then
      mkdir -p ${CHE_DATA}/storage/db
      mv ${CHE_DATA}/db/che.mv.db ${CHE_DATA}/storage/db/che.mv.db
      echo "Database has been successfully moved to the new path"
    fi
  fi
}

init() {
  ### Any variables with export is a value that native Tomcat che.sh startup script requires
  export CHE_IP=${CHE_IP}

  if [ -z "$CHE_HOME" ]; then
    if [ -f "/assembly/tomcat/bin/catalina.sh" ]; then
      export CHE_HOME="/assembly"
      echo "Found custom assembly in ${CHE_HOME}"
    else
      export CHE_HOME=$(echo /home/user/eclipse-che/)
      echo "Using embedded assembly in ${CHE_HOME}."
    fi
  else
    export CHE_HOME=$(echo ${CHE_HOME})
    echo "Using custom assembly from $CHE_HOME"
  fi

  ### We need to discover the host mount provided by the user for `/data`
  export CHE_DATA="/data"
  CHE_DATA_HOST=$(get_che_data_from_host)

  CHE_USER=${CHE_USER:-root}
  export CHE_USER=$CHE_USER
  if [ "$CHE_USER" != "root" ]; then
    if [ ! $(getent group docker) ]; then
      echo "!!!"
      echo "!!! Error: The docker group doesn't exist."
      echo "!!!"
      exit 1
    fi
    export CHE_USER_ID=${CHE_USER}
    sudo chown -R ${CHE_USER} ${CHE_DATA}
    sudo chown -R ${CHE_USER} ${CHE_HOME}
    sudo chown -R ${CHE_USER} ${CHE_LOGS_DIR}
  fi

  [ -z "$CHE_DATABASE" ] && export CHE_DATABASE=${CHE_DATA}/storage

  perform_database_migration

  # CHE_DOCKER_IP_EXTERNAL must be set if you are in a VM.
  HOSTNAME=${CHE_DOCKER_IP_EXTERNAL:-$(get_docker_external_hostname)}
  if has_external_hostname; then
    # Internal property used by Che to set hostname.
    export CHE_DOCKER_IP_EXTERNAL=${HOSTNAME}
  fi
  ### Necessary to allow the container to write projects to the folder
  [ -z "$CHE_WORKSPACE_STORAGE__MASTER__PATH" ] && export CHE_WORKSPACE_STORAGE__MASTER__PATH=${CHE_DATA}/workspaces
  [ -z "$CHE_WORKSPACE_STORAGE" ] && export CHE_WORKSPACE_STORAGE="${CHE_DATA_HOST}/workspaces"
  [ -z "$CHE_WORKSPACE_STORAGE_CREATE_FOLDERS" ] && export CHE_WORKSPACE_STORAGE_CREATE_FOLDERS=false

  # Cleanup no longer in use stacks folder, accordance to a new loading policy.
  if [[ -d "${CHE_DATA}"/stacks ]];then
    rm -rf "${CHE_DATA}"/stacks
  fi

  # A che property, which names the Docker network used for che + ws to communicate
  if [ -z "$CHE_DOCKER_NETWORK" ]; then
    NETWORK_NAME="bridge"
  else
    NETWORK_NAME=$CHE_DOCKER_NETWORK
  fi
  export JAVA_OPTS="${JAVA_OPTS} -Dche.docker.network=$NETWORK_NAME"
}

add_cert_to_truststore() {
  DEFAULT_JAVA_TRUST_STORE=${JAVA_HOME}/lib/security/cacerts
  DEFAULT_JAVA_TRUST_STOREPASS="changeit"

  JAVA_TRUST_STORE=/home/user/cacerts
  SELF_SIGNED_CERT=/home/user/self-signed.crt

  MESSAGE="Found a custom cert. Adding it to java trust store $JAVA_TRUST_STORE"
  if [ ! -f "$JAVA_TRUST_STORE" ]; then
    echo "$MESSAGE based on $DEFAULT_JAVA_TRUST_STORE"
    cp $DEFAULT_JAVA_TRUST_STORE $JAVA_TRUST_STORE
  else
    echo "$MESSAGE"
  fi

  echo "$1" > $SELF_SIGNED_CERT

  # make sure that owner has permissions to write and other groups have permissions to read
  chmod 644 "$JAVA_TRUST_STORE"

  echo yes | keytool -keystore $JAVA_TRUST_STORE -importcert -alias "$2" -file $SELF_SIGNED_CERT -storepass $DEFAULT_JAVA_TRUST_STOREPASS > /dev/null
  # set read-only permissions on keystore file
  chmod 444 "$JAVA_TRUST_STORE"
  set_truststore_system_variables "$JAVA_TRUST_STORE" "$DEFAULT_JAVA_TRUST_STOREPASS"
}

set_truststore_system_variables() {
  KEYSTORE_PATH=$1
  KEYSTORE_PASSWORD=$2
   if [[ "$JAVA_OPTS" != *"-Djavax.net.ssl.trustStore"* && "$JAVA_OPTS" != *"-Djavax.net.ssl.trustStorePassword"* ]]; then
    export JAVA_OPTS="${JAVA_OPTS} -Djavax.net.ssl.trustStore=$KEYSTORE_PATH -Djavax.net.ssl.trustStorePassword=$KEYSTORE_PASSWORD"
  fi
}

add_che_cert_to_truststore() {
  if [ "${CHE_SELF__SIGNED__CERT}" != "" ]; then
    add_cert_to_truststore "${CHE_SELF__SIGNED__CERT}" "HOSTDOMAIN"
  fi
}

add_public_certs_to_truststore() {
  JAVA_TRUST_STORE=/home/user/cacerts
  DEFAULT_JAVA_TRUST_STORE=${JAVA_HOME}/lib/security/cacerts
  DEFAULT_JAVA_TRUST_STOREPASS="changeit"

  if [ ! -f "$JAVA_TRUST_STORE" ]; then
    cp "$DEFAULT_JAVA_TRUST_STORE" "$JAVA_TRUST_STORE"
  fi

  chmod 644 "$JAVA_TRUST_STORE"

  CUSTOM_PUBLIC_CERTIFICATES="/public-certs"
  if [[ -d "$CUSTOM_PUBLIC_CERTIFICATES" && -n "$(find $CUSTOM_PUBLIC_CERTIFICATES -type f)" ]]; then
    FILES="$CUSTOM_PUBLIC_CERTIFICATES/*"
    for cert in $FILES
    do
      jks_import_ca_bundle "$cert" "$JAVA_TRUST_STORE" "$DEFAULT_JAVA_TRUST_STOREPASS"
    done
  fi

  chmod 444 "$JAVA_TRUST_STORE"
  set_truststore_system_variables "$JAVA_TRUST_STORE" "$DEFAULT_JAVA_TRUST_STOREPASS"
}

function jks_import_ca_bundle {
  CA_FILE=$1
  KEYSTORE_PATH=$2
  KEYSTORE_PASSWORD=$3

  if [ ! -f "$CA_FILE" ]; then
    # CA bundle file doesn't exist, skip it
    echo "Failed to import CA certificates from ${CA_FILE}. File doesn't exist"
    return
  fi

  bundle_name=$(basename "$CA_FILE")
  certs_imported=0
  cert_index=0
  tmp_file=/tmp/cert.pem
  is_cert=false
  while IFS= read -r line; do
    if [ "$line" == "-----BEGIN CERTIFICATE-----" ]; then
      # Start copying a new certificate
      is_cert=true
      cert_index=$((cert_index+1))
      # Reset destination file and add header line
      echo "$line" > ${tmp_file}
    elif [ "$line" == "-----END CERTIFICATE-----" ]; then
      # End of the certificate is reached, add it to trust store
      is_cert=false
      echo "$line" >> ${tmp_file}
      keytool -importcert -alias "${bundle_name}_${cert_index}" -keystore "$KEYSTORE_PATH" -file $tmp_file -storepass "$KEYSTORE_PASSWORD" -noprompt && \
      certs_imported=$((certs_imported+1))
    elif [ "$is_cert" == true ]; then
      # In the middle of a certificate, copy line to target file
      echo "$line" >> ${tmp_file}
    fi
  done < "$CA_FILE"
  echo "Imported ${certs_imported} certificates from ${CA_FILE}"
  # Clean up
  rm -f $tmp_file
}

get_che_data_from_host() {
  DEFAULT_DATA_HOST_PATH=/data
  CHE_SERVER_CONTAINER_ID=$(get_che_server_container_id)
  # If `docker inspect` fails $DEFAULT_DATA_HOST_PATH is returned
  echo $(docker inspect --format='{{(index .Volumes "/data")}}' $CHE_SERVER_CONTAINER_ID 2>/dev/null || echo $DEFAULT_DATA_HOST_PATH)
}

get_che_server_container_id() {
  # Returning `hostname` doesn't work when running Che on OpenShift/Kubernetes/Docker Cloud.
  # In these cases `hostname` correspond to the pod ID that is different from
  # the container ID
  echo $(basename "$(head /proc/1/cgroup || hostname)");
}

is_docker_for_mac_or_windows() {
  if uname -r | grep -q 'linuxkit'; then
    return 0
  elif uname -r | grep -q 'moby'; then
    return 0
  else
    return 1
  fi
}

get_docker_external_hostname() {
  if is_docker_for_mac_or_windows; then
    echo "localhost"
  else
    echo ""
  fi
}

has_external_hostname() {
  if [ "${HOSTNAME}" = "" ]; then
    return 1
  else
    return 0
  fi
}

# SITTERM / SIGINT
responsible_shutdown() {
  echo ""
  echo "Received SIGTERM"
  stop_che_server &
  wait ${PID}
  exit;
}

set -e
set +o posix

# setup handlers
# on callback, kill the last background process, which is `tail -f /dev/null` and execute the specified handler
trap 'responsible_shutdown' SIGHUP SIGTERM SIGINT

init
init_global_variables
set_environment_variables
add_che_cert_to_truststore
add_public_certs_to_truststore

# run che
start_che_server &

PID=$!

# See: http://veithen.github.io/2014/11/16/sigterm-propagation.html
wait ${PID}
wait ${PID}
EXIT_STATUS=$?

# wait forever
while true
do
  tail -f /dev/null & wait ${!}
done
