#!/bin/bash
#
# Copyright © 2014-2023 Claris International Inc.  All rights reserved.
#
# fmshelper	Startup script of FileMaker Server Helper
#
INSTALL_DIR="/opt/FileMaker/FileMaker Server"
SERVER_DIR="$INSTALL_DIR/Database Server"
BIN_DIR="$SERVER_DIR/bin"
PROG_NAME=fmshelper
PIDFILE="$SERVER_DIR/.${PROG_NAME}.pid"
CMD="$BIN_DIR/$PROG_NAME"
LOG_DIR="$INSTALL_DIR/Logs"
LOG_NAME="$LOG_DIR/${PROG_NAME}.log"
APACHE_PACKAGE=apache2
APACHE_PROC=/usr/sbin/apache2
JWPC_CATALINA_FILE="$INSTALL_DIR/Web Publishing/publishing-engine/jwpc-tomcat/bin/catalina.sh"


#
# Number of bits to shift to get size in MB from bytes
#
MB_SHIFT=20
MAX_LOG_SIZE_MB=60
MAX_LOG_SIZE=$((MAX_LOG_SIZE_MB << MB_SHIFT))
HIGH_THRESHOLD=85
#
# Special threshold if the log duration is less than 72 hours
#
DURATION_HOURS=72
LOW_THRESHOLD=75
STOP_TIMEOUT=120


formatDate() {
	# Gets current time, formatted
	date "+%Y-%m-%d %H:%M:%S.%3N %z"
}

callTag() {
	echo "$(formatDate) === $*" >>"$LOG_NAME"
}

logMessage() {
	echo "$(formatDate)	$*" >>"$LOG_NAME"
}

stopSystemApache() {
	callTag "${FUNCNAME[0]}()"
	#
	# Stop and disable system Apache if it is running/enabled.
	# System Apache will take over our HTTP service, resulting failure in  WebD and other web services
	#
	local result=0
	/usr/bin/dpkg -L $APACHE_PACKAGE >/dev/null 2>&1
	result=$?
	if [[ $result -eq 0 ]]; then
		/bin/systemctl is-active $APACHE_PACKAGE >/dev/null 2>&1
		result=$?
		if [[ $result -eq 0 ]]; then
			logMessage "Stop system HTTP server service..."
			/bin/systemctl stop $APACHE_PACKAGE >/dev/null 2>&1
			result=$?
			if [[ $result -ne 0 ]]; then
				logMessage "Fail to stop system HTTP server service..."
			fi
		fi

		/bin/systemctl is-enabled $APACHE_PACKAGE >/dev/null 2>&1
		result=$?
		if [[ $result -eq 0 ]]; then
			logMessage "Disable system HTTP server service..."
			/bin/systemctl disable $APACHE_PACKAGE >/dev/null 2>&1
			result=$?
			if [[ $result -ne 0 ]]; then
				logMessage "Fail to disable system HTTP server service..."
			fi
		fi
	fi
	callTag "${FUNCNAME[0]}() returns"
}

retireOldLogs() {
	callTag "${FUNCNAME[0]}()"
	local result=0
	local logSize
	logSize=$(stat -c %s "$LOG_NAME")
	#
	# Covert to MB
	#
	local sizeMB=$((logSize >> MB_SHIFT))
	#
	# Covert to % ratio
	#
	local ratio=$((sizeMB * 100 / MAX_LOG_SIZE_MB))

	#
	# Get the log file's duration in hours
	#
	local beginTime
	local endTime
	local beginSec
	local endSec
	beginTime=$(grep "=== start()" "$LOG_NAME" | head -1 | cut -d " " -f1-2)
	endTime=$(grep "=== stop()" "$LOG_NAME" | tail -1 | cut -d " " -f1-2)
	beginSec=$(date -d "$beginTime" +%s)
	endSec=$(date -d "$endTime" +%s)
	local elapseHour=$(((endSec - beginSec) / 3600))

	logMessage "Log file $LOG_NAME size: $logSize bytes ($sizeMB MB), threshold ratio: $ratio"
	logMessage "Log runs from $beginTime ($beginSec sec.) to $endTime ($endSec sec.), elapse time: $elapseHour hours "

	#
	# Retire old file if
	# 1. The size exceeds high threshold
	# 2. The size exeeeds low threshold but is less than high threshold and the log duration is less than the duration (72 hours days)
	#
	if [[ $ratio -lt $LOW_THRESHOLD ]]; then
		logMessage "Log file size is within thresholds."
	elif [[ $ratio -lt $HIGH_THRESHOLD ]]; then
		logMessage "Log file size is less than ${LOW_THRESHOLD}% but more than ${LOW_THRESHOLD}% of $MAX_LOG_SIZE"
		if [[ $elapseHour -lt $DURATION_HOURS ]]; then
			logMessage "Log duration is less than $DURATION_HOURS hours, will retire log file."
			result=1
		fi
	else
		logMessage "Log file size has exceeded ${HIGH_THRESHOLD}% of $MAX_LOG_SIZE, will retire log file."
		result=1
	fi

	if [[ $result -eq 1 ]]; then
		rm -f "${LOG_NAME}.old"
		mv "$LOG_NAME" "${LOG_NAME}.old"
		logMessage "Rename $LOG_NAME to ${LOG_NAME}.old"
	fi
	callTag "${FUNCNAME[0]}() returns"
}

ReconfigJvmOption()
{
	callTag "${FUNCNAME[0]}()"
	# update jwpc java option
	if [[ -f "${JWPC_CATALINA_FILE}" ]] ; then
		# get system memory and calculate the needed memory for JVM
		TotalMem=$(free -m | sed -n "2, 1p" | awk '{print $2}')
		FreeMem=$(free -m | sed -n "2, 1p" | awk '{print $4}')

		if [[ $TotalMem -lt 8192 ]]; then
			logMessage "Warnning: The memory of machine cannot fufill with the minimum - 8G!"		
		fi

		# set initial jvm max to 60% of total memory
		JvmMax=$(expr $TotalMem \* 3 / 5)
		if [[ $JvmMax -lt 500 ]] ; then # set max cap to 500  -lt 500 for micro, small instance. Assuming we have virtual memory 
			JvmMax=$((500))
		fi

		# set initial jvm min to quarter of total memory
		JvmMin=$(expr $TotalMem / 4)
		if [[ $JvmMin -gt 2000 ]] ; then # set min cap to 2000 for medium, large
			JvmMin=$((2000))
		fi

		if [[ $JvmMax -gt $FreeMem ]]; then
			logMessage "Warnning: The free memory is not enough, the performance of Jvm maybe influenced!"		
		fi


		if cat /proc/1/cgroup | grep docker > /dev/null 2>&1 ; then
			# in a docker container
			JvmMaxDocker=$(cat /sys/fs/cgroup/memory/memory.limit_in_bytes)
			JvmMaxDocker=$(($JvmMaxDocker/(1024**2) * 3 / 5))
			if [[ $JvmMax -gt $JvmMaxDocker ]]; then
				JvmMax=$JvmMaxDocker
			fi

			JvmMin=$((512))
			if [[ $JvmMin -gt $JvmMax ]]; then
				JvmMin=$JvmMax
			fi
			
			logMessage "Installing in a Docker Container: min:$JvmMin max:$JvmMax"
		fi
		

		MinJvmParam="-Xms${JvmMin}M"
		MaxJvmParam="-Xmx${JvmMax}M"

		sed -i -e "s/-Xms[0-9]*[MmGgKk]/$MinJvmParam/" -e "s/-Xmx[0-9]*[MmGgKk]/$MaxJvmParam/" "$JWPC_CATALINA_FILE"
	else
		logMessage "Warnning: CATALINA file cannot be found!"
	fi	
	callTag "${FUNCNAME[0]}() returns"
}

start() {
	callTag "${FUNCNAME[0]}()"
	local result=0
	local logSize
	logSize=$(stat -c %s "$LOG_NAME")
	local sizeMB=$((logSize >> MB_SHIFT))
	local ratio=$((sizeMB * 100 / MAX_LOG_SIZE_MB))
	logMessage "Log file $LOG_NAME size: $logSize bytes ($sizeMB MB), threshold ratio: $ratio"

	if [[ -f "$CMD" ]]; then
		stopSystemApache
		ReconfigJvmOption

		logMessage "Starting FileMaker Server Helper $CMD..."

		"$CMD" >>"$LOG_NAME" 2>&1
		result=$?
		if [[ $result -eq 0 ]]; then
			sleep 2
			local pid
			pid=$(pgrep -f "$CMD")
			if [[ -n $pid ]]; then
				logMessage "FileMaker Server Helper started successfully with process ID $pid"
				result=0
			else
				logMessage "FileMaker Server Helper started but failed to get process ID."
				result=1
			fi
		else
			logMessage "Fail to start FileMaker Server Helper..."
			result=1
		fi
	else
		logMessage "FileMaker Server Helper $CMD is not present."
		result=1
	fi
	callTag "${FUNCNAME[0]}() returns $result"
	return $result
}

stop() {
	callTag "${FUNCNAME[0]}()"

	local count=0
	local result=0
	local procID=0
	local timeout=$STOP_TIMEOUT
	local sleeptime=2
	local stopped=0

	if [[ -f "$PIDFILE" ]]; then
		procID=$(cat "$PIDFILE")
	else
		logMessage "FileMaker Server Helper PID file $PIDFILE does not exist, get runtime PID ..."
		procID=$(pgrep -f "$CMD")
	fi

	if [[ -n $procID ]]; then
		logMessage "Stopping FileMaker Server Helper process $procID..."
		#
		# Send a default SIGTERM signal to helper
		#
		kill "$procID" >>"$LOG_NAME" 2>&1

		#
		# Wait for helper session group to go away.
		#
		while [ $timeout -gt 0 ]; do
			count=$(pgrep -cs "$procID")
			if [[ $count -eq 0 ]]; then
				stopped=1
				break 1
			else
				logMessage "Time left for FileMaker Server Helper to stop: $timeout, process count=$count."
				timeout=$((timeout - sleeptime))
				sleep $sleeptime
			fi
		done

		if [[ $stopped -eq 1 ]]; then
			logMessage "FileMaker Server Helper process $procID has been stopped successfully..."
			result=0
		else
			logMessage "Failed to stop FileMaker Server Helper, send SIGKILL signal to process $procID..."
			#
			# Send a SIGKILL signal to terminate the helper session, which includes its child processes
			#
			pkill -9 -s "$procID" >>"$LOG_NAME" 2>&1
			result=0
			sleep $sleeptime
		fi

		#
		# Log trimmer is launched by a script and runs as daemon, not a direct child process of the helper,
		# therefore we need to handle it separately.
		#
		procID=$(pgrep fmslogtrimmer)
		if [[ -n $procID ]]; then
			logMessage "Send SIGKILL signal to fmslogtrimmer process $procID..."
			kill -9 "$procID" >>"$LOG_NAME" 2>&1
		fi

		#
		# Make sure all Apache processes are gone when helper is gone
		#
		count=$(pgrep -c -f "$APACHE_PROC")
		if [[ $count -eq 0 ]]; then
			logMessage "HTTP Server $APACHE_PROC is stopped..."
		else
			#
			# Get root Apache process ID and the list of its child processes, e.g. apache2 and rotatelogs
			#
			local rootApachePID
			rootApachePID=$(pgrep -f "$APACHE_PROC" | head -1)

			logMessage "Stopping HTTP Server process ID: ${rootApachePID}... "
			#
			# Send a SIGTERM signal to the first Apache process, which launches child Apache processes
			#
			kill "$rootApachePID" >>"$LOG_NAME" 2>&1
			sleep $sleeptime
			count=$(pgrep -cs "$rootApachePID")
			if [[ $count -eq 0 ]]; then
				logMessage "HTTP Server is stopped successfully... "
			else
				logMessage "Send SIGKILL signal to HTTP Server... "
				#
				# Send a SIGKILL signal to stop Apache process group
				#
				pkill -9 -s "$rootApachePID" >>"$LOG_NAME" 2>&1
			fi
		fi
	else
		logMessage "Cannot find FileMaker Server Helper process ID from $PIDFILE and cannot get it from pgrep, either..."
		result=0
	fi

	#
	# Remove PID file after the helper is stopped, either successfully or by force
	#
	rm -f "$PIDFILE"

	retireOldLogs
	callTag "${FUNCNAME[0]}() returns $result"
	return $result
}

#
# Actually this is never called becasue the helper service configurtion file does not
# have entries to trigger this method
#
status() {
	callTag "${FUNCNAME[0]}()"
	local result=0
	local procID
	if [[ -f "$PIDFILE" ]]; then
		procID=$(cat "$PIDFILE")
	else
		logMessage "FileMaker Server Helper PID file $PIDFILE does not exist, get runtime PID..."
		procID=$(pgrep -f "$CMD")
	fi

	if [[ -n $procID ]]; then
		logMessage "FileMaker Server Helper is running as process $procID ..."
	else
		logMessage "FileMaker Server Helper is not running..."
		result=1
	fi

	callTag "${FUNCNAME[0]}() returns $result"
	return $result
}

restart() {
	callTag "${FUNCNAME[0]}()"
	local result=0
	stop
	start
	result=$?
	callTag "${FUNCNAME[0]}() returns $result"
	return $result
}

. /lib/lsb/init-functions

callTag "$0 is called with command '$1'"

RETURN_CODE=0
if [[ -d "$SERVER_DIR" ]]; then
	case "$1" in
	start)
		start
		RETURN_CODE=$?
		;;
	stop)
		stop
		RETURN_CODE=$?
		;;
	status)
		status "$PIDFILE" $PROG_NAME
		RETURN_CODE=$?
		;;
	restart)
		restart
		RETURN_CODE=$?
		;;
	*)
		echo "Usage: $0 {start|stop|status|restart}"
		RETURN_CODE=2
		;;
	esac
else
	logMessage "FileMaker Server directory $SERVER_DIR does not exist..."
	RETURN_CODE=1
fi

callTag "$0 returns $RETURN_CODE"
exit $RETURN_CODE
