#!/usr/local/bin/cbsd
#v12.0.9
CBSDMODULE="node"
MYARG="mode"
MYOPTARG="node port rootkeyfile cbsdkeyfile pw header allinfo display ip"
MYDESC="Manipulate or show information for remote nodes"
ADDHELP="mode = add, remove, list, status\n\
node = remote node ip or fqdn\n\
port = ssh port of remote node\n\
ip = IP address for update\n\
rootkeyfile = path to id_rsa for root access\n\
pw = password of cbsd user from remote node (when cbsdkeyfile= is not used)\n\
cbsdkeyfile = path to cbsd user id_rsa insead of pw\n\
header = print header in node list\n\
allinfo = 1 (default) show all info for nodelist, 0 - only nodename\n\
display= list by comma for column. Default: nodename,ip,port,keyfile,status,idle\n\
mode=show,update - show or modify details about node\n"
EXTHELP="wf_node"

. ${subrdir}/nc.subr

readconf node.conf

. ${cbsdinit}

. ${nodes}

[ -z "${display}" ] && display="nodename,ip,port,keyfile,status,idle"

#remove commas for loop action on header
mydisplay=$( echo ${display} | ${TR_CMD} ',' '  ' )

# upper for header
myheader=$( echo ${mydisplay} | ${TR_CMD} '[:lower:]' '[:upper:]' )

cluster_nodes_show()
{
	# todo: figure out why output is different like this
	cmd node ls && return 0
}

show_header()
{
	local _header="${H1_COLOR}${BOLD}${myheader}${N0_COLOR}"
	[ ${header} -eq 1 ] && ${ECHO} "${_header}"
}

getpw()
{
	local oldmodes=$( ${STTY_CMD} -g )
	pw=""

	trap "stty ${oldmodes}" HUP INT ABRT BUS TERM EXIT

	printf "${BOLD}Enter password of cbsd user on ${N2_COLOR}${node}${N0_COLOR}${BOLD}:${N0_COLOR} "
	while [ -z "${pw}" ]; do
		stty -echo
		set -e
		read pw
		set +e
	done

	stty ${oldmodes}
	echo
}

nodeadd()
{
	local _res
	local _myip

	[ -z "${node}" ] && err 1 "${N1_COLOR}Empty node${N0_COLOR}"
	[ -z "${port}" ] && port=22
	if [ -z "${cbsdkeyfile}" ]; then
		[ -z "${pw}" ] && getpw
	else
		[ ! -r "${cbsdkeyfile}" ] && err 1 "${N1_COLOR}node: no such file here: ${N2_COLOR}${cbsdkeyfile}${N0_COLOR}"
	fi
	[ -z "${rootkeyfile}" ] && rootkeyfile="/root/.ssh/id_rsa"

	iptype ${node} >/dev/null 2>&1
	_ret=$?

	case ${_ret} in
		1|2)
			# ip4 or ip6
			_myip="${node}"
			;;
		*)
			_myip=$( resolvhost ${node} )
			[ -z "${_myip}" ] && err 1 "${N1_COLOR}Can't resolv IP for ${node} hostname. Use IP address.${N0_COLOR}"
			node=${_myip}
			;;
	esac

	iptype ${_myip} >/dev/null 2>&1
	_ret=$?

	# if natip is not valid IPv4, assume it is NIC variable.
	# so try to find out first IPv4 for aliasing
	case ${_ret} in
		1)
			if [ -n "${cbsdkeyfile}" ]; then
				# ssh key based
				CBSD_SSH_CMD="${SSH_CMD} -i ${cbsdkeyfile} -oStrictHostKeyChecking=no -oUserKnownHostsFile=/dev/null -oBatchMode=yes -oPort=${port} -l ${cbsduser} ${node}"
			else
				# pw based auth
				CBSD_SSH_CMD="cbsdssh ${node} ${port} ${cbsduser} ${pw}"
			fi
			proto="IPv4"
			;;
		2)
			if [ -n "${cbsdkeyfile}" ]; then
				# ssh key based
				CBSD_SSH_CMD="${SSH_CMD} -6 -i ${cbsdkeyfile} -oStrictHostKeyChecking=no -oUserKnownHostsFile=/dev/null -oBatchMode=yes -oPort=${port} -l ${cbsduser} ${node}"
			else
				CBSD_SSH_CMD="cbsdssh6 ${node} ${port} ${cbsduser} ${pw}"
			fi
			proto="IPv6"
			;;
		*)
			err 1 "${N1_COLOR}Unknown IP type: ${N2_COLOR}${_myip}${N0_COLOR}"
		;;
	esac

	${ECHO} "${N1_COLOR}Connecting to ${node} via ${proto}...${N0_COLOR}"
	NODENAME=$( ${CBSD_SSH_CMD} /usr/local/bin/cbsd getinfo -q nodename )
	code=$?

	case ${code} in
		0)
			${ECHO} "${N1_COLOR}${node} has nodename: ${N2_COLOR}${NODENAME}${N0_COLOR}"
			;;
		2)
			err 1 "${N1_COLOR}Bad password or system user: ${NODENAME}${N0_COLOR}"
			;;
		*)
			err 1 "${N1_COLOR}Connection problem (code ${code}): ${N2_COLOR}${NODENAME}${N0_COLOR}"
			;;
	esac

	[ -z "${NODENAME}" ] && err 1 "${N1_COLOR}No nodename found. Check remote cbsd settings${N0_COLOR}"

	MD5NAME=$( ${MD5_CMD} -qs ${NODENAME} )
	if [ -n "${cbsdkeyfile}" ]; then
		nodeaddkey md5name=${MD5NAME} ip=${node} port=${port} cbsdkeyfile=${cbsdkeyfile} > ${DEBLOG} 2>&1
	else
		nodeaddkey md5name=${MD5NAME} ip=${node} port=${port} pw=${pw} > ${DEBLOG} 2>&1
	fi
	_res=$?

	case ${_res} in
		0)
			${ECHO} "${N1_COLOR}Added successful: ${N2_COLOR}${node}${N0_COLOR}"
			${ECHO} "${W1_COLOR}!Warning! ${H5_COLOR}In a multi-node configuration your commands can be run on remote servers ${W1_COLOR}!Warning!${N0_COLOR}"
			${ECHO} "${W1_COLOR}!Warning! ${H5_COLOR}Be careful with [xjb]remove-like command ${W1_COLOR}!Warning!${N0_COLOR}"
			LOCALKEY="${rsshdir}/${MD5NAME}.id_rsa"
			${TOUCH_CMD} ${rsshdir}/${NODENAME}.node
			${SYSRC_CMD} -qf ${rsshdir}/${NODENAME}.node SSHKEY=${LOCALKEY} > /dev/null
			${SYSRC_CMD} -qf ${rsshdir}/${NODENAME}.node IP=${node} > /dev/null
			${SYSRC_CMD} -qf ${rsshdir}/${NODENAME}.node PORT=${port} > /dev/null
			${CHOWN_CMD} ${cbsduser} ${rsshdir}/${NODENAME}.node
			IP=$( cbsdsqlro nodes SELECT ip FROM nodelist WHERE nodename=\"${NODENAME}\" )

			external_exec_master_node_script -a add.d -n ${NODENAME} -i ${node} -p ${port} -k ${LOCALKEY}

			if [ -z "${IP}" ]; then
				cbsdsqlrw nodes "INSERT INTO nodelist ( nodename, ip, port, keyfile, rootkeyfile, invfile ) VALUES ( \"${NODENAME}\", \"${node}\", \"${port}\", \"${LOCALKEY}\", \"${rootkeyfile}\", \"inv.${NODENAME}.sqlite\" )"
			else
				${ECHO} "${N1_COLOR}Already exist in database, updating: ${N2_COLOR}${node}${N0_COLOR}"
				cbsdsqlrw nodes DELETE FROM nodelist WHERE nodename=\"${NODENAME}\"
				cbsdsqlrw nodes "INSERT INTO nodelist ( nodename, ip, port, keyfile, rootkeyfile, invfile ) VALUES ( \"${NODENAME}\", \"${node}\", \"${port}\", \"${LOCALKEY}\", \"${rootkeyfile}\", \"inv.${NODENAME}.sqlite\" )"
			fi
			idle_update ${NODENAME}
			retrinv node=${NODENAME} tryoffline=1
			return ${_res}
			;;
		1)
			cat ${DEBLOG}
			err ${_res} "${N1_COLOR}Error: Bad password${N0_COLOR}"
			;;
		2)
			[ -f "${DEBLOG}" ] && ${CAT_CMD} ${DEBLOG}
			err ${_res} "${N1_COLOR}Error: No key found or wrong hostname. Please run: 'cbsd initenv' on remote machine${N0_COLOR}"
			;;
		*)
			cat ${DEBLOG}
			err ${_res} "${N1_COLOR}Error: Unkown error${N0_COLOR}"
			;;
	esac
}

nodedel()
{
	local _descext="descr role domain notes location" _res

	[ -z "${node}" ] && err 1 "${N1_COLOR}Empty node${N0_COLOR}"
	NODECONF="${rsshdir}/${node}.node"

	if [ -f "${NODECONF}" ]; then
		. ${NODECONF}
		[ -f ${SSHKEY} ] && ${RM_CMD} -f ${SSHKEY}
		${RM_CMD} -f ${NODECONF}
	else
		${ECHO} "${N1_COLOR}No such node config: ${N2_COLOR}${NODECONF}${N0_COLOR}"
	fi

	[ -f "${dbdir}/${node}.sqlite" ] && ${RM_CMD} -f "${dbdir}/${node}.sqlite"
	_res=$( cbsdsqlrw nodes DELETE FROM nodelist WHERE nodename=\"${node}\" )

	#descriptions die too
	${FIND_CMD} ${dbdir}/nodedescr -type f -depth 1 -maxdepth 1 -name ${node}.\*.descr -delete
	for i in ${_descext}; do
		[ -f "${dbdir}/nodedescr/${node}.${i}" ] && ${RM_CMD} -f "${dbdir}/nodedescr/${node}.${i}"
	done
	err 0 "${N1_COLOR}Removed${N0_COLOR}"
}

show_nodes()
{
	if [ "${mod_cbsd_cluster_enabled}" = "YES" -a -z "${MOD_CBSD_CLUSTER_DISABLED}" ]; then
		cluster_nodes_show && return
	fi

	OIFS=${IFS}
	local sqldelimer IFS


	if [ ${allinfo} -eq 0 ]; then
		cbsdsqlro nodes SELECT nodename FROM nodelist
	else
		show_header
		sqldelimer="|"
		IFS="|"
		cbsdsqlro nodes SELECT nodename,ip,port,keyfile,idle FROM nodelist | while read nodename ip port keyfile idle; do
			IFS=${OIFS}

			conv_idle "${idle}"

			for _i in ${mydisplay}; do
				_val=""

				eval _val="\$$_i"
				[ -z "${_val}" ] && _val="-"

				if [ -z "${_status}" ]; then
					_status="${N0_COLOR}${N2_COLOR}${_val}"
				else
					_status="${_status} ${_val}"
				fi
			done

			${ECHO} "${_status}${N0_COLOR}"
			IFS="|"
			_status=
		done
	fi
	unset sqldelimer
}

shownode() {
	local _res

	local sqldelimer=" "
	_res=$( cbsdsqlro nodes SELECT nodename FROM nodelist WHERE nodename=\"${node}\" )

	[ -z "${_res}" ] && err 1 "${N1_COLOR}No such node in databases: ${N2_COLOR}${node}${N0_COLOR}"

	${ECHO} "${BOLD}  Summary statistics for ${node}  ${N0_COLOR}"
	${ECHO} "${BOLD}  ====================================  ${N0_COLOR}"
	echo

	if [ ! -f "${dbdir}/${node}.sqlite" ]; then
		${ECHO} "${N1_COLOR}No such inventory for: ${N2_COLOR}${node}${N0_COLOR}. ${N1_COLOR}Try to obtain it...${N0_COLOR}"
		retrinv node=${node} tryoffline=1
		[ ! -f "${dbdir}/${node}.sqlite" ] && err 1 "${N1_COLOR}No such inventory for: ${N2_COLOR}${node}${N0_COLOR}"
	fi

	local _nodeinv="hostname,nodeip,fs,ncpu,physmem,freemem,disks,cpumode,cpufreq,nics,osrelease,hostarch"

	sqldelimer="|"

	eval $( cbsdsqlro ${node} SELECT ${_nodeinv} FROM local | while read hostname nodeip fs ncpu physmem freemem disks cpumodel cpufreq nics osrelease hostarch; do
		echo "local hostname=\"\${hostname}\""
		echo "local nodeip=\"\${nodeip}\""
		echo "local fs=\"${fs}\""
		echo "local ncpu=\"${ncpu}\""
		echo "local physmem=\"${physmem}\""
		echo "local freemem=\"${freemem}\""
		echo "local disks=\"${disks}\""
		echo "local cpumodel=\"${cpumodel}\""
		echo "local cpufreq=\"${cpufreq}\""
		echo "local nics=\"${nics}\""
		echo "local osrelease=\"${osrelease}\""
		echo "local hostarch=\"${hostarch}\""
	done )

	sqldelimer=" "

	local jailsnum=$( cbsdsqlro ${node} SELECT count\(jname\) FROM jails WHERE emulator != \"bhyve\" )
	local vmsnum=$( cbsdsqlro ${node} SELECT count\(jname\) FROM jails WHERE emulator = \"bhyve\" )

	[ -z "${jailsnum}" ] && jailsnum="0"
	[ -z "${vmsnum}" ] && vmsnum="0"

	${ECHO} "${BOLD}Nodename: ${N2_COLOR}${node}${N0_COLOR}"
	${ECHO} "${BOLD}Hostname: ${N2_COLOR}${hostname}${N0_COLOR}"
	${ECHO} "${BOLD}Node IP: ${N2_COLOR}${nodeip}${N0_COLOR}"
	${ECHO} "${BOLD}FS: ${N2_COLOR}${fs}${N0_COLOR}"
	${ECHO} "${BOLD}Total core's: ${N2_COLOR}${ncpu}${N0_COLOR}"
	${ECHO} "${BOLD}CPU Model: ${N2_COLOR}${cpumode}${N0_COLOR}"
	${ECHO} "${BOLD}CPU Frequency: ${N2_COLOR}${cpufrq}${N0_COLOR}"
	${ECHO} "${BOLD}Total physmem: ${N2_COLOR}${physmem}${N0_COLOR}"
	${ECHO} "${BOLD}Free mem: ${N2_COLOR}${freemem}${N0_COLOR}"
	${ECHO} "${BOLD}NICs: ${N2_COLOR}${nics}${N0_COLOR}"
	echo
	${ECHO} "${BOLD}OS Release: ${N2_COLOR}${osrelease}${N0_COLOR}"
	${ECHO} "${BOLD}OS arch: ${N2_COLOR}${hostarch}${N0_COLOR}"
	${ECHO} "${BOLD}Total VMs: ${N2_COLOR}${vmsnum}${N0_COLOR}"
	${ECHO} "${BOLD}Total Jails: ${N2_COLOR}${jailsnum}${N0_COLOR}"
	echo
	[ ${jailsnum} -ne 0 ] && ${ECHO} "${BOLD}List of ${node} jails:${N0_COLOR}" && jls node=${node} display=jname,host_hostname,status && echo
	[ ${vmsnum} -ne 0 ] && ${ECHO} "${BOLD}List of ${node} VMs:${N0_COLOR}" && bls node=${node} display=jname,status && echo
}

update_node()
{
	local _port _ip

	while getopts "i:p:" opt; do
		case "${opt}" in
			p)
				_port="${OPTARG}"
				cbsdsqlrw nodes UPDATE nodelist SET port=\"${_port}\" WHERE nodename=\"${node}\"
			;;
			i)
				_ip="${OPTARG}"
				cbsdsqlrw nodes UPDATE nodelist SET ip=\"${_ip}\" WHERE nodename=\"${node}\"
			;;
		esac
		shift $(($OPTIND - 1))
	done

	return 0
}

# MAIN
curtime=$( ${DATE_CMD} +%s )

[ -z "${allinfo}" ] && allinfo=1
[ -z "${header}" ] && header=1

case "${mode}" in
	"add")
		nodeadd
		;;
	"list")
		show_nodes | ${COLUMN_CMD} -t
		;;
	"remove")
		nodedel
		;;
	"show")
		[ -z "${node}" ] && err 1 "${N1_COLOR}Please specify: ${N2_COLOR}node=${N0_COLOR}"
		shownode
		;;
	"update")
		[ -z "${node}" ] && err 1 "${N1_COLOR}Please specify ${N2_COLOR}node=${N0_COLOR}"
		modified=0
		if [ -n "${port}" ]; then
			update_node -p "${port}"
			modified=1
		fi
		if [ -n "${ip}" ]; then
			update_node -i "${ip}"
			modified=1
		fi
		if [ ${modified} -eq 1 ]; then
			idle_update ${node} > /dev/null 2>&1
			retrinv node=${node} tryoffline=1
		else
			err 1 "${N1_COLOR}Please specify ${N2_COLOR}ip=${N1_COLOR} or ${N2_COLOR}port=${N1_COLOR} to update${N0_COLOR}"
		fi
		;;
	*)
		err 1 "${N1_COLOR}Unknown mode${N0_COLOR}"
		;;
esac
