#!/bin/ksh93

typeset -r VERSION='1.0' FPROG=${.sh.file} PROG=${FPROG##*/} SDIR=${FPROG%/*}\
	CFD='/etc/freeswitch'
typeset SD='/usr/share/freeswitch/conf'

function showUsage {
	[[ -n $1 ]] && X='-?' ||  X='--man'
	getopts -a ${PROG} "${ print ${USAGE} ; }" OPT $X
}

function createOpt {
	if [[ -e /opt/freeswitch ]]; then
		print -u2 "'/opt/freeswitch' already exists."
		return 1
	fi
	${DRY} mkdir -p /opt/freeswitch || return 2
	${DRY} cd /opt/freeswitch
	${DRY} ln -s /etc etc
	${DRY} ln -s /etc conf
	${DRY} ln -s /usr/bin bin
	${DRY} ln -s /usr/include include
	${DRY} ln -s /usr/lib lib
	${DRY} ln -s /usr/share share
	${DRY} ln -s /var/log/freeswitch log
	${DRY} ln -s /var var
}

function removeOpt {
	typeset F L=
	if [[ ! -d /opt/freeswitch ]]; then
		print -u2 "Directory '/opt/freeswitch' does not exists."
		return 1
	fi
	for F in etc conf bin include lib share log var ; do
		[[ -h /opt/freeswitch/$F ]] && L+=" /opt/freeswitch/$F" && continue
		print -u2 "'/opt/freeswitch/$F' is not a symlink. Nothing changed."
		return 2
	done
	${DRY} rm -f $L || return 3
	${DRY} rmdir /opt/freeswitch
}

function checkConf {
	typeset T M U G
	integer E=0

	if [[ ! -e ${OPTS[DST]}/freeswitch.xml ]] ; then
		M='FreeSWITCH is propably not yet configured!'
		M+="\n\nSee '${FPROG} -h' to get an idea"
		M+="\nhow to populate the config directory ${OPTS[DST]}/."
	fi
	systemctl cat freeswitch | while read X T ; do
		if [[ ${X:0:5} == 'User=' ]]; then
			U=${X:5}
			[[ -n $G ]] && break
		elif [[ ${X:0:6} == 'Group=' ]]; then
			G=${X:6}
			[[ -n $U ]] && break
		fi
	done
	if [[ -n $G ]]; then
		T=${ getent group $G ; }
		if [[ -z $T ]]; then
			[[ -n $M ]] && M+='\n\n'
			M+='Your freeswitch service is configured to run as group '
			M+="'$G', but there is no such group on the system. "
			M+="Create it using e.g.: 'groupadd --system $G'."
		fi
	fi
	if [[ -n $U ]]; then
		T=${ getent passwd $U ; }
		if [[ -z $T ]]; then
			[[ -n $M ]] && M+='\n\n'
			M+='Your freeswitch service is configured to run as user '
			M+="'$U', but there is no such user on the system. "
			M+="Create it using e.g.: 'useradd --gid ${G:-freeswitch} -d /var/freeswitch --shell /bin/bash -c \"freeswitch daemon\" --system $U'."
		fi
	fi
	T=/var/log/freeswitch/freeswitch.log
	if [[ -f $T ]] && grep -q 'Failure to connect to CORE_DB sofia_reg_ext' $T
	then
		# See: https://docs.bigbluebutton.org/2.2/troubleshooting#freeswitch-fails-to-bind-to-ipv4
		print -u2 'Clearing the FreeSWITCH database.'
		rm -rf /var/lib/freeswitch/db/*
	fi
	[[ -z $M ]] && return 0
	if (( OPTS[CHECK] )); then
		print -u2 "$M"
		/usr/bin/logger -p err ${ print "$M"; }
	fi
	return 255
}

function doMain {
	typeset X F T P

	if (( OPTS[OPT] == 1 )); then
		createOpt
		return $?
	elif (( OPTS[OPT] == 2 )); then
		removeOpt
		return $?
	fi

	if [[ -n ${OPTS[VAR]} ]]; then
		if [[ ${OPTS[VAR]} == 'default' ]]; then
			OPTS[VAR]=
		elif [[ ${OPTS[VAR]} != 'bbb' ]]; then
			print -u2 "Unknown variant '${OPTS[VAR]}' - exiting."
			return 1
		fi
	fi
	[[ -z ${OPTS[DST]} ]] && OPTS[DST]=${CFD}
	[[ -z ${OPTS[SRC]} ]] && OPTS[SRC]="${SD}"
	(( OPTS[CHECK] ))  && { checkConf ; return $? ; }

	if (( ! OPTS[FORCE] )) && checkConf ; then
		print -u2 '
'"${OPTS[DST]}"'/ is probably already populated.
Either remove it or use option -f to force a removal and re-initialization.
Exiting.
'
		return 2
	fi
	${DRY} cd ${OPTS[SRC]} || return 3
	if [[ -n ${OPTS[VAR]} && ! -d ${OPTS[VAR]} ]]; then
		print -u2 "Variant directory '${OPTS[VAR]}' does not exist."
		return 3
	fi
	T=${ mktemp /tmp/fs.XXXXXX; }
	[[ -z $T ]] && \
		print -u2 'Unable to create temp file - exiting.' && return 3

	if [[ -d ${OPTS[DST]} ]]; then
		${DRY} find -P ${OPTS[DST]} -type l -exec rm {} +
		${DRY} rm -rf ${OPTS[DST]}/*
	else
		${DRY} mkdir -p ${OPTS[DST]} || return 4
	fi
	find -P . -name bbb -prune -o -name tls -prune -o -print | \
		${DRY} cpio -puvmd --no-preserve-owner ${OPTS[DST]}
	ln -s /var/lib/freeswitch/tls ${OPTS[DST]}/tls
	if [[ -n ${OPTS[VAR]} ]]; then
		typeset -a DEFAULTS2REMOVE
		if [[ -f ${OPTS[VAR]}/D2R ]]; then
			. ${OPTS[VAR]}/D2R
			typeset -a A
			for F in "${DEFAULTS2REMOVE[@]}"; do
				P="${OPTS[DST]}/${F//../}"
				[[ -e $P ]] && A+=( "$P" )
			done
			[[ -n $A ]] && ${DRY} rm -f "${A[@]}"
		fi
		for F in ${OPTS[VAR]}/*.xml ; do
			X=${F//:/$'/'}
			${DRY} cp -p "$F" "${OPTS[DST]}/${X#*/}"
		done
	fi
	if [[ -z ${DRY} ]]; then
		print "Configured variant: ${OPTS[VAR]:-default}\nby ${.sh.file}" \
			>"${OPTS[DST]}/README"
	fi
	F="${OPTS[DST]}/autoload_configs/event_socket.conf.xml"
	if [[ -e $F ]]; then
		P=${ openssl rand -hex 8 ; }
		cp $F $T
		[[ -n $P ]] && \
			${DRY} sed -ie '/name="password"/ s|value=.*|value="'"$P"'"/>|' $T

		if (( OPTS[IP4] )); then
			${DRY} sed -i -e '/listen-ip/ s/::/127.0.0.1/' $T
			P="${OPTS[DST]}/sip_profiles/internal-ipv6.xml" 
			[[ -e $P ]] && mv "$P" "${P}.disabled"
			P="${OPTS[DST]}/sip_profiles/external-ipv6.xml" 
			[[ -e $P ]] && mv "$P" "${P}.disabled"
		fi
		if [[ -s $T ]] && ! cmp -s $F $T; then
			${DRY} cp $T $F
			${DRY} chmod 0640 $F
			${DRY} chown root:daemon $F
		fi
		print -u2 '
WARNING: FreeSWITCH Event Socket Layer - default password "ClueCon"
Remember to adjust /usr/share/bbb-fsesl-akka/conf/application.conf,
the value of freeswitch.esl.password, as well as in
/usr/local/bigbluebutton/bbb-webrtc-sfu/config/default.yml the 
freeswitch.esl_password (default: ClueCon) to the same password value of
'"$F"'!
'
	fi
	return 0
}

USAGE="[-?${VERSION}"' ]
[-copyright?Copyright (c) 2020 Jens Elkner. All rights reserved.]
[-license?CDDL 1.0]
[+NAME?'"${PROG}"' - simple helper script to setup/reset \b'"${CFD}"'/\b.]
[+DESCRIPTION?This little script helps to setup/initialize the FreeSwitch configuration directory \b'"${CFD}"'/\b. If instructed it removes everything in '"${CFD}"'/, [re-]]populates it with the default config files and finally applies the changes of a certain variant. The variant can be specified via option \b-v ...\b]
[+?To avoid copying over valuable configs, the script checks, whether there is a \b'"${CFD}"'/freeswitch.xml\b. If so it does nothing unless option \b-f\b is specified.]
[+?The user running this script needs write permission for \b'"${CFD}"'/\b and its sub-directories, except when option \b-o\b or \b-O\b is used - in this case the user needs write permission for \b/opt/\b.]
[h:help?Print this help and exit.]
[F:functions?Print a list of all functions available.]
[T:trace]:[functionList?A comma separated list of functions of this script to trace (convinience for troubleshooting).] 
[n:dry?Do nothing bot show, what would be done.]
[+?]
[4:ip4?Use IPv4 address \b127.0.0.1\b instead of IPv6 address \b::\b to listen for Event Socket Layer (ESL) messages. IF ESL is not accessible, this is probably the reason for it.]
[c:check?Just do a very simple check, whether freeswitch config directory got populated. If not, issue a warning and exit with 255. Otherwise exit with 0. Mainly used for the freeswitch systemd service, because poor systemd does not inform the user about unmet conditions and triggered assertions.]
[d:destdir]:[path?Use the given \apath\a as configuration directory to initialize.]
[f:force?Do not care and remove recursively \b'"${CFD}"'/*\b without any questions and makes free the way to [re-]]populate this directory.]
[s:sourcedir]:[path?Use the given \apath\a as source directory for getting default config files instead of '"${SD}"'/.]
[O:noopt?Remove the directory created using option \b-o\b w/o asking any questions.]
[o:opt?Setup a \b/opt/freeswitch\b directory and symlink its sub-directories to the related FreeSwitch directories, so that pathes usually used by BigBlueButton work as well. Exit when done.]
[v:variant]:[name?If \b'"${CFD}"'/\b gets initialized or reset, use the variant with the given \aname\a (right now, \bbbb\b is the only supported variant). If not given, the default configuration will be used.]
[+FILES?Some important config files wrt. IP and password relative to \b/etc/freeswitch/\b:]{
	[+?vars.xml  (stun server, default local IP, SIP ports)]
	[+?autoload_configs/switch.conf  (rtp ports, zrtp)]
	[+?autoload_configs/acl.conf.xml  (lan IPs)]
	[+?autoload_configs/verto.conf.xml  (IPs and codecs)]
	[+?autoload_configs/modules.conf.xml  (modules to load)]
	[+?autoload_configs/event_socket.conf.xml  (localhost IP and ESL password)]
	[+?sip_profiles/external[-ipv6]].xml  (IP, ports for Websockets, 3pcc)]
	[+?sip_profiles/internal[-ipv6]].xml  (IP, ports for Websockets, 3pcc)]
}
'
unset DRY OPTS OPT; typeset DRY= ; typeset -A OPTS
X="${ print ${USAGE} ; }"
while getopts "${X}" OPT ; do
	case ${OPT} in
		h) showUsage ; exit 0 ;;
		T)	if [[ ${OPTARG} == 'ALL' ]]; then
				typeset -ft ${ typeset +f ; }
			else
				typeset -ft ${OPTARG//,/ }
			fi
			;;
		F) typeset +f && exit 0 ;;
		n) DRY='print --' ;;

		4) OPTS[IP4]=1 ;;
		c) OPTS[CHECK]=1 ;;
		d) OPTS[DST]="${OPTARG}" ;;
		f) OPTS[FORCE]=1 ;;
		s) OPTS[SRC]="${OPTARG}" ;;
		O) OPTS[OPT]=2 ;;
		o) OPTS[OPT]=1 ;;
		v) OPTS[VAR]="${OPTARG}" ;;
		*) showUsage 1 ; exit 1 ;;
	esac
done

X=$((OPTIND-1))
shift $X && OPTIND=1
unset X

doMain "$@"
