#!/usr/bin/env bash

# Script should be run as root user
if [[ $EUID -ne 0 ]]; then
	echo "Error: diagnostics script must be run as the root user."
	exit 1
fi

read -r -d '' USAGE <<- EOF
Usage: rstudio-server run-diagnostics [--output-file=<path>]

Options:
	-o, --output-file  An optional location where the RStudio diagnostics
	                   report should be written. Defaults to a file in
	                   the /tmp/rstudio-diagnostics directory.
	
EOF

# Like 'cat', but also echos file names as a header,
# prints a warning if the file contains control characters,
# and screens out content that is known to contain security keys/passwords
vcat () {

	for FILE in "$@"; do

                baseFile="${FILE##*/}"
                ESCAPED_FILE=$(LC_ALL=C printf "%q" "${FILE}")
                if [ "${ESCAPED_FILE}" != "${FILE}" ]; then
                        echo "WARNING: File '${FILE}' [${ESCAPED}] contains unexpected characters." 1>&6
                fi
                LC_ALL=C printf "=== Config file: %q:\n" "${FILE}"
                ls -l ${FILE}

                case "$baseFile" in

                        database.*)
                                echo "=== begin file contents:"
                                cat -v "${FILE}" | sed -e 's/password=.*$/password=*** omitted by rstudio-diagnostics ***/'
                        ;;

                        launcher.kubernetes.*)
                                echo "=== begin file contents:"
                                cat -v "${FILE}" | sed -e 's/auth-token=.*$/auth-token=*** omitted by rstudio-diagnostics ***/'
                                echo "=== end file contents"
                        ;;

                        *.crt*|*.key*|secure-cookie-key|*.pub*|*.pem*)
                                echo "=== secure file omitted from rstudio-diagnostics ==="
                        ;;


                        *)
                                echo "=== begin file contents:"
                                cat -v "${FILE}" 2> /dev/null
                                echo "=== end file contents"
                        ;;

                esac

                echo ""

	done

}

initConfig() {
        if [[ "$RSTUDIO_CONFIG_DIR" != "" ]] ; then
           echo "RStudio config dir: RSTUDIO_CONFIG_DIR=${RSTUDIO_CONFIG_DIR}"
        elif [[ "$XDG_CONFIG_DIRS" != "" ]]; then
                IFS=':' read -r -a config_dirs_array <<< "$XDG_CONFIG_DIRS"
                for config_dir in "${config_dirs_array[@]}"
                do
                   if [ -f "$config_dir/rserver.conf" ] ; then
                       echo "with XDG_CONFIG_DIRS=${XDG_CONFIG_DIRS} RStudio config dir detected as: $config_dir" 
                       RSTUDIO_CONFIG_DIR=$config_dir
                       break
                   fi
                done
        fi

        if [[ "$RSTUDIO_CONFIG_DIR" == "" ]] ; then
           if [[ "$XDG_CONFIG_DIRS" != "" ]] ; then
               echo "Warning: XDG_CONFIG_DIRS search path set to: ${XDG_CONFIG_DIRS} but rserver.conf not found in path"
           fi
           echo "Default RStudio config dir: /etc/rstudio"
           RSTUDIO_CONFIG_DIR=/etc/rstudio
        fi
        echo ""
}


# parse command line arguments
while [ "$#" -ne "0" ]; do
	
	case "$1" in

	-h|--help)
		echo "${USAGE}"
		exit 0
	;;

	--output-file=*|-o=*)
		OUTPUT_FILE="${1#*=}"
	;;

	-o|--output-file)
		OUTPUT_FILE="$2"
		shift
	;;

	*)
		echo "Error: unexpected argument '$1'."
		exit 1
	;;

	esac

	shift

done

if [ -z "${OUTPUT_FILE}" ]; then
	DATE="$(date +"%y%m%d%H%M%S")"
	OUTPUT_FILE="/tmp/rstudio-diagnostics/rsp-diagnostics-report-$(hostname)-${DATE}.txt"
fi

mkdir -p "$(dirname "${OUTPUT_FILE}")"

h1 () {
	echo ""
	echo "-------------------------------"
	echo "$1"
	echo "-------------------------------"
	echo ""
}

h2 () {
	echo ""
	echo "## $1"
}

cat_config () {

	find "$1/" -maxdepth 1 ! -type d ! -name "*.rpm" ! -name "*.deb" | while IFS= read -r f; do
		vcat "${f}"
		echo ""
        done

}

cat_log () {

	find "$1/" -maxdepth 1 ! -type d -mtime -14 | while IFS= read -r f; do
                echo "=== ${f}:"
		cat "${f}"
		echo "==="
                echo ""
        done

}

echo "Generating diagnostics report ..."
echo ""

# save stdout, stderr descriptors
exec 5<&1
exec 6<&2

# start redirecting output to file
exec 1> "${OUTPUT_FILE}"
exec 2>&1

echo "=============================================="
echo "Started rstudio-diagnostics at $(date)"
echo ""
echo "Posit Workbench Version: $(rstudio-server version)"

echo "Hostname: $(hostname)"

h1 "Operating system information"
tail -n+1 -v /etc/*-release

if [ -f /etc/lsb-release ]; then
	DISTRO=$(. /etc/lsb-release; echo "${DISTRIB_ID}")
elif [ -f /etc/os-release ]; then
	ID=$(. /etc/os-release; echo "${ID}")
	case "${ID}" in
	*suse*)   DISTRO="SUSE"   ;;
	*debian*) DISTRO="Debian" ;;
	*centos*) DISTRO="CentOS" ;;
	*rhel*)   DISTRO="RedHat" ;;
	esac
elif [ -f /etc/centos-release ]; then
	DISTRO="CentOS"
elif [ -f /etc/redhat-release ]; then
	DISTRO="RedHat"
fi

if [ -z "${DISTRO}" ]; then
	DISTRO="RedHat"
	echo "Distribution unable to be determined; RedHat set as default."
fi

h2 "uname -a"
uname -a

h1 "Mounts"

h2 "Mount point space usage"
df -h | sort

h2 "Mount point inode usage"
df -i | sort

if command -v nfsstat &> /dev/null; then
	h2 "nfsstat -m"
	nfsstat -m || echo "<nfsstat -m had exit code $?>"
fi

h1 "R Installation"
echo "which R: $(which R)"
echo "which gcc: $(which gcc)"
if [[ "$(which R 2> /dev/null)" ]] && [[ "$(R RHOME 2> /dev/null)" ]]; then

	R_HOME=$(R RHOME)

	echo "R_HOME: $R_HOME"
	R --version

	h1 "R config files"

	# Parse contents of r-versions to detect all R_HOME paths and store in array
	R_CONFIG_DIR=($(cat /var/lib/rstudio-server/r-versions | grep R_HOME | cut -d '"' -f4))

	# append /etc to each directory in array
	R_CONFIG_DIR=( "${R_CONFIG_DIR[@]/%//etc}" )

	for m in "${R_CONFIG_DIR[@]}"
	do
		find "${m}/" -maxdepth 1 ! -type d | while IFS= read -r f
		do
			vcat "${f}" 2> /dev/null
			echo ""
		done
	done
else
	echo "R not found."
fi

h1 "RStudio Server"

h2 "Status of rstudio-server package"
case "${DISTRO}" in
"RedHat" | "CentOS")
	yum info rstudio-server
;;
"Ubuntu" | "Debian")
	dpkg -s rstudio-server
;;
"SUSE")
	zypper info rstudio-server
;;
esac

h2 "rstudio-server status, and verify-installation"
rstudio-server status
rstudio-server verify-installation

h2 "rstudio-launcher version and status"
rstudio-launcher version
rstudio-launcher status

h1 "RStudio Server config files"

initConfig

cat_config "$RSTUDIO_CONFIG_DIR"
vcat /etc/profile /etc/environment

h2 "PAM Configuration"
vcat /etc/pam.conf /etc/security/pam_env.conf
PAM_CONFIG_DIR="/etc/pam.d"
cat_config "$PAM_CONFIG_DIR"

LB_SCRIPT="/usr/lib/rstudio-server/bin/rserver-balancer"
h1 "Custom load-balancing script"
if [ -f "$LB_SCRIPT" ]; then
	vcat "$LB_SCRIPT"
else
	echo "<${LB_SCRIPT} does not exist>"
fi

h1 "License manager status"
if ping -c 1 www.wyday.com &> /dev/null; then
	echo "OK: ping to www.wyday.com successful"
else
	echo "NOTE: ping to www.wyday.com unsuccessful"
fi

h2 "Online license status"
rstudio-server license-manager status

h2 "Offline license status"
rstudio-server license-manager status-offline

h1 "Machine environment"
env

h2 "ldd rserver"
ldd /usr/lib/rstudio-server/bin/rserver

h2 "ldd rsession"
ldd /usr/lib/rstudio-server/bin/rsession

h1 "R environment outside RStudio (default R)"

h2 "Sys.info()"
Rscript -e 'Sys.info()'

h2 "sessionInfo()"
Rscript -e 'sessionInfo()'

h2 "Sys.getenv()"
Rscript -e 'Sys.getenv()'

h2 ".libPaths()"
Rscript -e '.libPaths()'

h2 "R RHOME:"
echo "${R_HOME}"

if [ -f "/var/log/messages" ]; then
	SYS_LOG_PATH="/var/log/messages"
elif [ -f "/var/log/syslog" ]; then
	SYS_LOG_PATH="/var/log/syslog"
fi

h1 "RStudio messages in the system log: ${SYS_LOG_PATH}"
if [ -f "$SYS_LOG_PATH" ]; then
	grep -E "rstudio|rserver|rsession|rworkspaces" "${SYS_LOG_PATH}"
else
	echo "<failed to determine path to system logs>"
fi

h1 "RStudio server monitor log: rserver, rsession, rworkspaces, rserver-launcher, rserver-admin errors/warnings"

RSP_LOG_PATH="/var/lib/rstudio-server/monitor/log/rstudio-server.log"
if [ -f "$RSP_LOG_PATH" ]; then
	cat "$RSP_LOG_PATH"
else
	echo "Monitor default log file: <${RSP_LOG_PATH} does not exist>"
fi

if [ -f "$SYS_LOG_PATH" ]; then
        h1 "RStudio system messages"
	grep "rstudio\(-.*\)\?-launcher" "${SYS_LOG_PATH}"
else
	echo "<failed to determine path to system logs>"
fi

# also include any files seen in a configured log directory
if [ -f /etc/rstudio/logging.conf ]; then
	RSP_LOG_DIRS=$(grep -E '^log-dir=' /etc/rstudio/logging.conf | cut -d'=' -f2)
	for RSP_LOG_DIR in "${RSP_LOG_DIRS[@]}"; do
                if [ "${RSP_LOG_DIR}" == "" ]; then
                        RSP_LOG_DIR="/var/log/rstudio/rstudio-server"
                fi
		if [ -d "${RSP_LOG_DIR}" ]; then
                        h1 "Log files from logging.conf log-dir: ${RSP_LOG_DIR}"
			cat_log "${RSP_LOG_DIR}"
		fi
	done
fi

h1 "Job Launcher debug logs"
LAUNCHER_CONF_FILE="/etc/rstudio/launcher.conf"
LAUNCHER_LOG_PATH="/var/log/rstudio/launcher"
declare -a LAUNCHER_CONF_FILES
cat $LAUNCHER_CONF_FILE | while read LINE; do
      if [[ $LINE =~ "logging-dir" ]]; then
            launcherLoggingDir=$(echo $LINE | sed -n /^logging-dir=/s/^logging-dir=//p)
            if [ -z "$launcherLoggingDir" ]; then
              continue
            fi
            LAUNCHER_LOG_PATH="${launcherLoggingDir}"
	elif [[ $LINE =~ "config-file" ]]; then
		configFile=$(echo $LINE | sed -n /^config-file=/s/^config-file=//p)
		if [ -z "$configFile" ]; then
			continue
		fi
		LAUNCHER_CONF_FILES+=("${configFile}")
	fi
done

LAUNCHER_LOG_FILES=($(find $LAUNCHER_LOG_PATH -name "*.log"))
for LOG_FILE in "${LAUNCHER_LOG_FILES[@]}"; do 
        h1 "Launcher log file: $LOG_FILE"
	cat "$LOG_FILE"
done

for CONF_FILE in "${LAUNCHER_CONF_FILES[@]}"; do
	cat $CONF_FILE | while read LINE; do
            if [[ $LINE =~ "logging-dir" ]]; then
                  launcherLoggingDir=$(echo $LINE | sed -n /^logging-dir=/s/^logging-dir=//p)
                  if [ -z "$launcherLoggingDir" ]; then
                        continue
                  fi
			PLUGIN_LOG_FILES=($(find $scratchPath -name "*.log"))
			for LOG_FILE in "${PLUGIN_LOG_FILES[@]}"; do
                                h1 "Launcher plugin log file: $LOG_FILE"
				cat $LOG_FILE
			done
		fi
	done
done

h1 "Java environment"

h2 "whereis java"
whereis java

h2 "java -version"
java -version

h2 "JAVA_HOME"
echo "${JAVA_HOME-"<unset>"}"

h2 "PATH"
echo "${PATH-"<unset>"}"

if command -v jps &> /dev/null; then
	echo "JVM Status:"
	jps -lvm
fi

echo ""
echo "** Compare this data to the contents of the ${R_CONFIG_DIR}/javaconf file above."
echo ""

h1 "Network connections"

cat <<- EOF
http_proxy:  ${http_proxy-"<unset>"}
https_proxy: ${https_proxy-"<unset>"}
all_proxy:   ${all_proxy-"<unset>"}
EOF

if ping -c 1 www.google.com &> /dev/null; then
	echo "OK: ping to www.google.com successful"
else
	echo "NOTE: ping to www.google.com unsuccessful"
fi

h2 "GET headers"
curl -vs www.google.com 1> /dev/null

h2 "Check websocket handshake headers"
PORT=$(sed -nE 's/^www-port[^{0-9}]+//p' < /etc/rstudio/rserver.conf)
[ -z "${PORT}" ] && PORT=8787
curl -i -N -H "Connection: Upgrade" -H "Upgrade: websocket" -H "Host: localhost:$PORT" -H "Origin: localhost:$PORT" http://localhost:$PORT

echo ""
echo "Finished diagnostics report at $(date)."

# restore stdout, stderr
exec 1>&5
exec 2>&6

# notify that we're all done
cat <<- EOF

Diagnostics report has been generated: ${OUTPUT_FILE}

Please note that the diagnostics report may contain sensitive data.
You may wish to sanitize data that you feel is sensitive, but please us know if you have done so.

EOF

if [ "$(stat -c '%s' "${OUTPUT_FILE}")" -gt 52428800 ]; then

	cat <<- EOF
	The generated report is quite large ($(du -h "${OUTPUT_FILE}" | cut -f1)).
	Consider compressing and using our Support Upload tool to submit this file:

	http://apps.rstudio.com/SupportUpload/

	Please let us know you have done this, along with the name of the uploaded file.
	EOF

fi

