#!/bin/bash
#
# TapeTrack Framework Server diagnostic helper.
#
# This script prints local service, process, database, network, and security
# checks that are useful when diagnosing a TapeTrack server installation.
# It is diagnostic only and does not update service state, configuration,
# database files, firewall rules, SELinux policy, or any other system setting.
#
# Copyright (c) 2026 GazillaByte Pty Ltd.

SERVICE_NAME="${TAPETRACK_SERVICE:-tapetrack.service}"
SERVER_BIN="${TAPETRACK_SERVER:-/usr/sbin/TMSS10}"
PING_BIN="${TAPETRACK_PING:-/usr/bin/TMSS10Ping}"
SYSCONFIG_FILE="${TAPETRACK_SYSCONFIG:-/etc/sysconfig/tapetrack}"
SERVICES_FILE="${TAPETRACK_SERVICES_FILE:-/etc/services}"
DEFAULT_DB_HOME="/var/tapetrack/db"
DEFAULT_PORT="5000"

if [ -t 1 ] && [ -z "${NO_COLOR:-}" ]
then
	RED=$(printf '\033[31m')
	GREEN=$(printf '\033[32m')
	YELLOW=$(printf '\033[33m')
	BLUE=$(printf '\033[34m')
	BOLD=$(printf '\033[1m')
	RESET=$(printf '\033[0m')
else
	RED=""
	GREEN=""
	YELLOW=""
	BLUE=""
	BOLD=""
	RESET=""
fi

case "${LANG:-}${LC_ALL:-}${LC_CTYPE:-}" in
	*UTF-8*|*utf8*|*utf-8*)
		OK_MARK="✓"
		FAIL_MARK="✗"
		WARN_MARK="!"
		;;
	*)
		OK_MARK="OK"
		FAIL_MARK="FAIL"
		WARN_MARK="WARN"
		;;
esac

TEST_NO=0
TMSS10_OPTIONS_VALUE=""
DB_HOME="$DEFAULT_DB_HOME"
PORT="${TAPETRACK_INFO_PORT:-}"

print_heading()
{
	printf "%s%s%s\n" "$BOLD" "$1" "$RESET"
}

print_test()
{
	TEST_NO=$((TEST_NO + 1))
	printf "\n%s[%02d] %s%s\n" "$BLUE" "$TEST_NO" "$1" "$RESET"
	printf "     %s\n" "$2"
}

print_pass()
{
	printf "     %s%s%s %s\n" "$GREEN" "$OK_MARK" "$RESET" "$1"
}

print_fail()
{
	printf "     %s%s%s %s\n" "$RED" "$FAIL_MARK" "$RESET" "$1"
}

print_warn()
{
	printf "     %s%s%s %s\n" "$YELLOW" "$WARN_MARK" "$RESET" "$1"
}

print_output()
{
	if [ -n "$1" ]
	then
		printf "%s\n" "$1" | sed 's/^/       /'
	fi
}

strip_outer_quotes()
{
	printf "%s" "$1" | sed -e 's/^"//' -e 's/"$//' -e "s/^'//" -e "s/'$//"
}

get_sysconfig_options()
{
	if [ -r "$SYSCONFIG_FILE" ]
	then
		awk '
			/^[[:space:]]*#/ { next }
			/^[[:space:]]*TMSS10_OPTIONS[[:space:]]*=/ {
				line = $0
				sub(/^[^=]*=/, "", line)
				gsub(/^[[:space:]]+|[[:space:]]+$/, "", line)
				print line
			}
		' "$SYSCONFIG_FILE" | tail -n 1
	fi
}

get_option_value()
{
	local option="$1"
	local options="$2"
	local word
	local next=0

	for word in $options
	do
		if [ "$next" -eq 1 ]
		then
			printf "%s" "$word"
			return 0
		fi

		if [ "$word" = "$option" ]
		then
			next=1
		fi
	done

	return 1
}

resolve_tmss10_ping()
{
	if [ -x "$PING_BIN" ]
	then
		printf "%s" "$PING_BIN"
	elif command -v TMSS10Ping >/dev/null 2>&1
	then
		command -v TMSS10Ping
	else
		return 1
	fi
}

if [ -r "$SYSCONFIG_FILE" ]
then
	TMSS10_OPTIONS_VALUE=$(strip_outer_quotes "$(get_sysconfig_options)")
fi

if [ -n "$TMSS10_OPTIONS_VALUE" ]
then
	CONFIG_DB_HOME=$(get_option_value "-h" "$TMSS10_OPTIONS_VALUE")
	CONFIG_PORT=$(get_option_value "-p" "$TMSS10_OPTIONS_VALUE")

	if [ -n "$CONFIG_DB_HOME" ]
	then
		DB_HOME="$CONFIG_DB_HOME"
	fi

	if [ -z "$PORT" ] && [ -n "$CONFIG_PORT" ]
	then
		PORT="$CONFIG_PORT"
	fi
fi

if [ -z "$PORT" ]
then
	PORT="$DEFAULT_PORT"
fi

DB_CONFIG_FILE="${DB_HOME}/DB_CONFIG"

print_heading "TapeTrack service information"

print_test \
	"Checking systemd state for ${SERVICE_NAME}" \
	"Confirms whether systemd can see, start, and track the TapeTrack service."

if command -v systemctl >/dev/null 2>&1
then
	SYSTEMD_STATE=$(systemctl is-active "$SERVICE_NAME" 2>&1)
	SYSTEMD_STATE_RC=$?
	SYSTEMD_ENABLED=$(systemctl is-enabled "$SERVICE_NAME" 2>&1)
	SYSTEMD_ENABLED_RC=$?
	SYSTEMD_SHOW=$(systemctl show "$SERVICE_NAME" \
		-p ActiveState \
		-p SubState \
		-p UnitFileState \
		-p MainPID \
		-p ExecMainStatus \
		-p FragmentPath \
		-p ExecStart \
		--no-pager 2>&1)

	if [ "$SYSTEMD_STATE_RC" -eq 0 ]
	then
		print_pass "${SERVICE_NAME} is active."
	else
		print_fail "${SERVICE_NAME} is not active: ${SYSTEMD_STATE}"
	fi

	if [ "$SYSTEMD_ENABLED_RC" -eq 0 ]
	then
		print_pass "${SERVICE_NAME} is enabled: ${SYSTEMD_ENABLED}"
	else
		print_warn "${SERVICE_NAME} enablement state: ${SYSTEMD_ENABLED}"
	fi

	print_output "$SYSTEMD_SHOW"
else
	print_warn "systemctl not found; skipping systemd service check."
fi

print_test \
	"Checking ${SYSCONFIG_FILE}" \
	"Shows the service options that should be passed into TMSS10 at startup."

if [ -r "$SYSCONFIG_FILE" ]
then
	print_pass "${SYSCONFIG_FILE} is readable."
	if [ -n "$TMSS10_OPTIONS_VALUE" ]
	then
		print_pass "TMSS10_OPTIONS=${TMSS10_OPTIONS_VALUE}"
	else
		print_warn "No active TMSS10_OPTIONS entry found."
	fi
	print_pass "Effective database home: ${DB_HOME}"
	print_pass "Effective service port: ${PORT}"
	print_output "$(sed 's/[[:space:]]*$//' "$SYSCONFIG_FILE")"
else
	print_warn "${SYSCONFIG_FILE} is not readable; using defaults."
	print_warn "Effective database home: ${DB_HOME}"
	print_warn "Effective service port: ${PORT}"
fi

print_test \
	"Checking ${SERVICES_FILE} for a tapetrack service entry" \
	"Confirms whether getservbyname(\"tapetrack\", \"tcp\") can resolve the default service port."

if [ -r "$SERVICES_FILE" ]
then
	SERVICES_ENTRY=$(awk '$1 == "tapetrack" { print }' "$SERVICES_FILE")
	if [ -n "$SERVICES_ENTRY" ]
	then
		if printf "%s\n" "$SERVICES_ENTRY" | awk -v port="${PORT}/tcp" '{ for (i = 2; i <= NF; i++) if ($i == port) found = 1 } END { exit found ? 0 : 1 }'
		then
			print_pass "Found tapetrack entry for ${PORT}/tcp."
		else
			print_warn "Found tapetrack entry, but not for ${PORT}/tcp."
		fi
		print_output "$SERVICES_ENTRY"
	else
		print_warn "No tapetrack entry found in ${SERVICES_FILE}."
	fi
else
	print_warn "${SERVICES_FILE} is not readable; skipping service-name check."
fi

print_test \
	"Checking TapeTrack binaries and shared library dependencies" \
	"Confirms that the server and ping client are installed and dynamically link cleanly."

if [ -x "$SERVER_BIN" ]
then
	print_pass "${SERVER_BIN} is executable."
else
	print_fail "${SERVER_BIN} is missing or not executable."
fi

PING_RESOLVED=$(resolve_tmss10_ping)
if [ -n "$PING_RESOLVED" ]
then
	print_pass "${PING_RESOLVED} is executable."
else
	print_fail "TMSS10Ping is missing or not executable."
fi

if [ -x "$SERVER_BIN" ] && command -v ldd >/dev/null 2>&1
then
	LDD_OUTPUT=$(ldd "$SERVER_BIN" 2>&1)
	MISSING_LIBS=$(printf "%s\n" "$LDD_OUTPUT" | awk '/not found/ { print }')
	if [ -n "$MISSING_LIBS" ]
	then
		print_fail "Missing shared library dependency detected."
		print_output "$MISSING_LIBS"
	else
		print_pass "No missing shared libraries reported by ldd."
	fi
elif [ -x "$SERVER_BIN" ]
then
	print_warn "ldd not found; skipping shared library check."
fi

print_test \
	"Checking installed TMSS10 version" \
	"Captures the server build string for support and upgrade checks."

if [ -x "$SERVER_BIN" ]
then
	VERSION_OUTPUT=$("$SERVER_BIN" -V 2>&1)
	VERSION_RC=$?
	if [ "$VERSION_RC" -eq 0 ]
	then
		print_pass "${SERVER_BIN} -V succeeded."
	else
		print_fail "${SERVER_BIN} -V failed with exit code ${VERSION_RC}."
	fi
	print_output "$VERSION_OUTPUT"
else
	print_fail "${SERVER_BIN} is not executable; skipping version check."
fi

print_test \
	"Checking database home ${DB_HOME}" \
	"Confirms that the expected database environment and key database files are present."

if [ -d "$DB_HOME" ]
then
	print_pass "${DB_HOME} exists."
else
	print_fail "${DB_HOME} does not exist."
fi

DB_REPORT=""
DB_MISSING=0
for DB_FILE in DB_CONFIG
do
	DB_PATH="${DB_HOME}/${DB_FILE}"
	if [ -r "$DB_PATH" ]
	then
		DB_REPORT="${DB_REPORT}${DB_FILE}: present and readable"$'\n'
	else
		DB_REPORT="${DB_REPORT}${DB_FILE}: missing or not readable"$'\n'
		DB_MISSING=$((DB_MISSING + 1))
	fi
done

if [ "$DB_MISSING" -eq 0 ]
then
	print_pass "Required database files are present."
else
	print_fail "${DB_MISSING} required database file(s) are missing or unreadable."
fi
print_output "$DB_REPORT"

print_test \
	"Checking active DB_CONFIG replication and log settings" \
	"Highlights settings that can make this server start as a read-only replication client."

if [ -r "$DB_CONFIG_FILE" ]
then
	DB_CONFIG_LINES=$(awk '
		/^[[:space:]]*#/ { next }
		/^[[:space:]]*(rep_set_priority|repmgr_site|set_lg_dir)[[:space:]]/ { print }
	' "$DB_CONFIG_FILE")

	if [ -n "$DB_CONFIG_LINES" ]
	then
		if printf "%s\n" "$DB_CONFIG_LINES" | awk '$1 == "rep_set_priority" && $2 == "0" { found = 1 } END { exit found ? 0 : 1 }'
		then
			print_warn "Active rep_set_priority 0 found; TMSS10 may start read-only as a replication client."
		else
			print_pass "No active rep_set_priority 0 found."
		fi
		print_output "$DB_CONFIG_LINES"
	else
		print_warn "No active rep_set_priority, repmgr_site, or set_lg_dir lines found."
	fi
else
	print_warn "${DB_CONFIG_FILE} is not readable; skipping DB_CONFIG check."
fi

print_test \
	"Checking for services listening on TCP port ${PORT}" \
	"Confirms whether TapeTrack, or another process, owns the expected service port."

if command -v ss >/dev/null 2>&1
then
	LISTENERS=$(ss -H -ltnup "( sport = :${PORT} )" 2>&1)
	LISTENERS_RC=$?
	if [ "$LISTENERS_RC" -eq 0 ] && [ -n "$LISTENERS" ]
	then
		print_pass "Found listener(s) on port ${PORT}."
		print_output "$LISTENERS"
	elif [ "$LISTENERS_RC" -eq 0 ]
	then
		print_fail "No listener found on port ${PORT}."
	else
		print_fail "Unable to inspect port ${PORT} with ss."
		print_output "$LISTENERS"
	fi
else
	print_warn "ss command not found; skipping port listener check."
fi

print_test \
	"Checking for running programs named TMSS10" \
	"Confirms whether the TapeTrack server process is currently running."

if command -v pgrep >/dev/null 2>&1
then
	PROCESSES=$(pgrep -a -x TMSS10 2>&1)
	PROCESSES_RC=$?
	if [ "$PROCESSES_RC" -eq 0 ] && [ -n "$PROCESSES" ]
	then
		PROCESS_PIDS=$(printf "%s\n" "$PROCESSES" | awk '{ print $1 }')
		print_pass "Found TMSS10 process(es)."
		print_output "$PROCESSES"
	else
		PROCESS_PIDS=""
		print_fail "No TMSS10 processes found."
	fi
else
	PROCESSES=$(ps -ef | awk '$8 ~ /(^|\/)TMSS10$/ { print }')
	PROCESS_PIDS=$(ps -ef | awk '$8 ~ /(^|\/)TMSS10$/ { print $2 }')
	if [ -n "$PROCESSES" ]
	then
		print_pass "Found TMSS10 process(es)."
		print_output "$PROCESSES"
	else
		print_fail "No TMSS10 processes found."
	fi
fi

print_test \
	"Checking TMSS10 process command line" \
	"Confirms the live process is using the expected options, especially the database home."

if [ -n "$PROCESS_PIDS" ]
then
	CMDLINE_OUTPUT=""
	for PID in $PROCESS_PIDS
	do
		if [ -r "/proc/${PID}/cmdline" ]
		then
			CMDLINE_OUTPUT="${CMDLINE_OUTPUT}${PID}: $(tr '\0' ' ' < "/proc/${PID}/cmdline")"$'\n'
		else
			CMDLINE_OUTPUT="${CMDLINE_OUTPUT}${PID}: /proc/${PID}/cmdline is not readable"$'\n'
		fi
	done
	print_pass "Collected TMSS10 command line information."
	print_output "$CMDLINE_OUTPUT"
else
	print_fail "No TMSS10 process found; skipping command line check."
fi

print_test \
	"Running TMSS10Ping against localhost" \
	"Confirms that the local TapeTrack endpoint accepts a client ping."

PING_RESOLVED=$(resolve_tmss10_ping)
if [ -n "$PING_RESOLVED" ]
then
	PING_OUTPUT=$("$PING_RESOLVED" localhost 2>&1)
	PING_RC=$?
	if [ "$PING_RC" -eq 0 ]
	then
		print_pass "TMSS10Ping localhost succeeded."
	else
		print_fail "TMSS10Ping localhost failed with exit code ${PING_RC}."
	fi
	print_output "$PING_OUTPUT"
else
	print_fail "TMSS10Ping not found; skipping ping check."
fi

print_test \
	"Checking recent ${SERVICE_NAME} journal messages" \
	"Looks for startup and login clues such as read-only mode, tapemaster updates, and DB errors."

if command -v journalctl >/dev/null 2>&1
then
	JOURNAL_OUTPUT=$(journalctl -u "$SERVICE_NAME" -n 160 --no-pager 2>&1)
	JOURNAL_RC=$?
	if [ "$JOURNAL_RC" -eq 0 ]
	then
		JOURNAL_MATCHES=$(printf "%s\n" "$JOURNAL_OUTPUT" | grep -Ei 'read-only|tapemaster|User Database|DB_CONFIG|DBENV|Unknown User|User-ID is undefined|Server started|DBENV->open' || true)
		if [ -n "$JOURNAL_MATCHES" ]
		then
			print_warn "Recent journal contains TapeTrack diagnostic clues."
			print_output "$JOURNAL_MATCHES"
		else
			print_pass "No matching diagnostic journal lines found in the last 160 entries."
		fi
	else
		print_warn "journalctl failed for ${SERVICE_NAME}."
		print_output "$JOURNAL_OUTPUT"
	fi
else
	print_warn "journalctl not found; skipping journal check."
fi

print_test \
	"Checking recent SELinux AVC denials" \
	"Looks for SELinux blocks involving TapeTrack binaries, service files, or database paths."

SELINUX_MODE=""
if command -v getenforce >/dev/null 2>&1
then
	SELINUX_MODE=$(getenforce 2>&1)
	print_pass "SELinux mode: ${SELINUX_MODE}"
else
	print_warn "getenforce not found; SELinux mode unknown."
fi

if [ "$SELINUX_MODE" = "Disabled" ]
then
	print_pass "SELinux is disabled; AVC denial check is not needed."
elif command -v ausearch >/dev/null 2>&1
then
	AVC_OUTPUT=$(ausearch -m AVC -ts recent 2>&1)
	AVC_MATCHES=$(printf "%s\n" "$AVC_OUTPUT" | grep -Ei 'TMSS10|tapetrack|/var/tapetrack|tapetrack.service' || true)
	if [ -n "$AVC_MATCHES" ]
	then
		print_warn "Recent SELinux AVC denials mention TapeTrack."
		print_output "$AVC_MATCHES"
	else
		print_pass "No recent TapeTrack-related AVC denials found."
	fi
else
	print_warn "ausearch not found; skipping AVC denial check."
fi

print_test \
	"Checking firewalld exposure for ${PORT}/tcp" \
	"Confirms whether remote clients can reach the TapeTrack port when firewalld is active."

if command -v firewall-cmd >/dev/null 2>&1
then
	FIREWALL_STATE=$(firewall-cmd --state 2>&1)
	if [ "$FIREWALL_STATE" = "running" ]
	then
		if firewall-cmd --query-port="${PORT}/tcp" >/dev/null 2>&1
		then
			print_pass "firewalld allows ${PORT}/tcp."
		else
			print_warn "firewalld is running, but ${PORT}/tcp is not explicitly open."
		fi
		FIREWALL_ACTIVE=$(firewall-cmd --list-all 2>&1)
		print_output "$FIREWALL_ACTIVE"
	else
		print_warn "firewalld is not running: ${FIREWALL_STATE}"
	fi
else
	print_warn "firewall-cmd not found; skipping firewalld check."
fi

exit 0
