#!/bin/bash
set -e
#
# HTCondor for Linux Quick Install script
#
# For the installation steps, including how to verify the contents of
# this file, see:
#
#   https://htcondor.readthedocs.io/en/latest/getting-htcondor/
#
# This script is meant for quick & easy install via:
#
#   $ sudo curl -fsSL https://get.htcondor.org | /bin/bash -s -- --no-dry-run
#

usage() {
	cat <<-EOF
	Specify only one of the following:
	--dist		: Display detected operating system and exit
	-h, --help	: Display this message and exit
	--download	: Download the tarball for a user-level installation and exit.
	--dry-run	: Do not install, just print commands [default]
	--no-dry-run	: Issue all the commands neeed to install HTCondor

	Installation options:
	--channel NAME	: Specify version channel to install; NAME can be
	    current: Most recent release with new features [default]
	    stable : Most recent release with only bug-fixes
	--password	: Set the password used to secure the pool.  Use only if
	          	  nobody else can log into the machine.  Otherwise specify
	          	  in the environment (GET_HTCONDOR_PASSWORD) or interactively.
	--shared-filesystem-domain NAME : Specifies that machines installed with
	    the same NAME share a filesystem.

	Installation options, specify only one:
	--minicondor            : Install a complete stand-alone HTCondor.  [default]
	--central-manager NAME	: Install as the central manager role.
	--submit NAME		: Install as the submit role.
	--execute NAME		: Install as the execute role.
	     The NAME of central manager's machine must be a DNS name resolvable
	     on all the machines in the pool, or an IP address.
	EOF
	exit 0
}

command_exists() {
	command -v "$@" > /dev/null 2>&1
}

install_config_file() {
	# Install a config file by either copying it from examples dir, or if
	# that does not exist, curl it.  Hopefully we do not need to curl it.
	if [ -r "/usr/share/condor/get_configs/$@" ]; then
		cp "/usr/share/condor/get_configs/$@" /etc/condor/config.d/.
		chmod 644 "/etc/condor/config.d/$@"
	fi
}

is_download() {
	if [ -z "$DOWNLOAD" ]; then
		return 1
	else
		return 0
	fi
}

is_dry_run() {
	if [ -z "$DRY_RUN" ]; then
		return 1
	else
		return 0
	fi
}

is_display_dist() {
	if [ -z "$DIST" ]; then
		return 1
	else
		return 0
	fi
}

is_darwin() {
	case "$(uname -s)" in
	*darwin* ) true ;;
	*Darwin* ) true ;;
	* ) false;;
	esac
}


# Check if this is a forked Linux distro
check_forked() {

	# Check for lsb_release command existence, it usually exists in forked distros
	if command_exists lsb_release; then
		# Check if the `-u` option is supported
		set +e
		lsb_release -a -u > /dev/null 2>&1
		lsb_release_exit_code=$?
		set -e

		# Check if the command has exited successfully, it means we're in a forked distro
		if [ "$lsb_release_exit_code" = "0" ]; then
			# Print info about current distro
			cat <<-EOF
			You're using '$lsb_dist' version '$dist_version'.
			EOF

			# Get the upstream release info
			lsb_dist=$(lsb_release -a -u 2>&1 | tr '[:upper:]' '[:lower:]' | grep -E 'id' | cut -d ':' -f 2 | tr -d '[:space:]')
			dist_version=$(lsb_release -a -u 2>&1 | tr '[:upper:]' '[:lower:]' | grep -E 'codename' | cut -d ':' -f 2 | tr -d '[:space:]')

			# Print info about upstream distro
			cat <<-EOF
			Upstream release is '$lsb_dist' version '$dist_version'.
			EOF
			if is_display_dist; then
				exit 0
			fi
		else
			if [ -r /etc/debian_version ] && [ "$lsb_dist" != "ubuntu" ] && [ "$lsb_dist" != "raspbian" ]; then
				if [ "$lsb_dist" = "osmc" ]; then
					# OSMC runs Raspbian
					lsb_dist=raspbian
				else
					# We're Debian and don't even know it!
					lsb_dist=debian
				fi
				dist_version="$(sed 's/\/.*//' /etc/debian_version | sed 's/\..*//')"
				case "$dist_version" in
					10)
						dist_version_number=10
						dist_version="buster"
					;;
					9)
						dist_version_number=9
						dist_version="stretch"
					;;
					8|'Kali Linux 2')
						dist_version_number=8
						dist_version="jessie"
					;;
				esac
			fi
		fi
	fi
	if is_display_dist; then
		cat <<-EOF
		You're using '$lsb_dist' version '$dist_version'.
		EOF
		exit 0
	fi
}


get_distribution() {
	lsb_dist=""
	# Every system that we officially support has /etc/os-release
	if [ -r /etc/os-release ]; then
		lsb_dist="$(. /etc/os-release && echo "$ID")"
	fi
	lsb_dist="$(echo "$lsb_dist" | tr '[:upper:]' '[:lower:]')"

	# An empty string for lsb_dist should be alright since the
	# case statements don't act unless you provide an actual value

	case "$lsb_dist" in

		ubuntu)
			if command_exists lsb_release; then
				dist_version="$(lsb_release --codename | cut -f2)"
			fi
			if [ -z "$dist_version" ] && [ -r /etc/lsb-release ]; then
				dist_version="$(. /etc/lsb-release && echo "$DISTRIB_CODENAME")"
			fi
		;;

		debian|raspbian)
			# Echoing VERSION_CODENAME out of /etc/os-release works
			# better on Devuan and is simpler... is that file missing on
			# raspbian?
			dist_version="$(sed 's/\/.*//' /etc/debian_version | sed 's/\..*//')"
			case "$dist_version" in
				10)
					dist_version="buster"
				;;
				9)
					dist_version="stretch"
				;;
				8)
					dist_version="jessie"
				;;
			esac
		;;

		centos|rhel)
			if [ -z "$dist_version" ] && [ -r /etc/os-release ]; then
				dist_version="$(. /etc/os-release && echo "$VERSION_ID")"
			fi
			dist_version="$(echo "$dist_version" | cut -d. -f1)"
		;;

		*)
			if command_exists lsb_release; then
				dist_version="$(lsb_release --release | cut -f2)"
			fi
			if [ -z "$dist_version" ] && [ -r /etc/os-release ]; then
				dist_version="$(. /etc/os-release && echo "$VERSION_ID")"
			fi
		;;

	esac

	# Check if this is a forked Linux distro
	check_forked
}

do_configure_firewall() {
	if command_exists firewall-cmd; then
		echo -e "\n# Open port 9618 for use by HTCondor"
		(
			if ! is_dry_run; then
				set -x
			fi
			$sh_c "firewall-cmd --zone=public --add-port=9618/tcp --permanent"
			$sh_c "firewall-cmd --reload"
		)
	fi
}

do_start_service() {
	# Start HTCondor via systemd if detected, else via just condor_master
	if [ "$(ps --pid 1 -o comm -h)" != 'systemd' ] && command_exists systemctl; then
		echo -e "\n# Start the HTCondor service in the background (without systemd)"
		(
			if ! is_dry_run; then
				set -x
			fi
			$sh_c "condor_master"
		)
	else
		echo -e "\n# Start the HTCondor service via systemd"
		(
			if ! is_dry_run; then
				set -x
			fi
			$sh_c "systemctl enable condor"
			$sh_c "systemctl start condor"
		)
	fi
}

do_token_security() {
	# This won't work in a pipeline (as we recommend using this script).
	if [ -z "${PASSWORD}" ]; then
		if ! is_dry_run; then
			echo -n "Enter a pool password: "
			read PASSWORD
		else
			PASSWORD="NONE"
			echo
			echo "SKIPPING PASSWORD PROMPT DURING DRY RUN"
			echo
		fi
	fi

	if [ -z "${PASSWORD}" ]; then
		echo
		echo "You must set a password!  Did you replace \$htcondor_password with a password?"
		echo
		exit 1
	fi

	# Remove the default 9.0 security configuration.  Arguably, the
	# get_htcondor metaknobs should instead check for SECURITY_MODEL == 9.0
	# and adjust accordingly, which might make microversion upgrades easier.
	$sh_c "rm -f /etc/condor/config.d/00-htcondor-9.0.config"

	# So 'echo' is a shell built-in, which means no command-line to show
	# up in 'ps' and leak the password.  You MUST do the 'echo' before the
	# '$sh_c', because the '$sh_c' itself will show up in 'ps'.
	echo -n "${PASSWORD}" | $sh_c "condor_store_cred add -c -i -"

	# Now issue myself a token.
	$sh_c "umask 0077; condor_token_create -identity condor@${CONDOR_HOST} > /etc/condor/tokens.d/condor@${CONDOR_HOST}"
}

do_install() {
	# Figure out the distribution and version we are running on.
	# This will set $lsb_dist and $dist_version.
	get_distribution

	user="$(id -un 2>/dev/null || true)"

	if is_dry_run; then
		sh_c="echo"
	elif [ "$user" != 'root' ]; then
		if command_exists sudo; then
			sh_c='sudo -E sh -c'
		elif command_exists su; then
			sh_c='su -c'
		else
			cat >&2 <<-'EOF'
			Error: this installer needs the ability to run commands as root.
			We are unable to find either "sudo" or "su" available to make this happen.
			EOF
			exit 1
		fi
	fi

	#
	# Check for a previous installation.  The instructions below remove
	# the HTCondor package(s) and whatever packages they depended on but
	# which no other package(s) did.  However, neither set of instructions
	# removes HTCondor's repositories, which will prevent downgrades.  I
	# thinks that's OK for now.
	#
	if [ -f "/etc/condor/condor_config" ]; then
		cat >&2 <<-'EOF'
			Error: HTCondor appears to have been installed previously on this system.

			You may update the existing install, or remove and then re-install.
		EOF
		case "$lsb_dist" in
		ubuntu|debian)
			PACKAGE=htcondor
			if [ -z "${ROLE}" ]; then
				PACKAGE=minihtcondor
			fi

			cat >&2 <<-EOF
				To update: $sh_c "apt-get update && apt-get upgrade ${PACKAGE}"
				To remove: $sh_c "apt-get -y remove --purge ${PACKAGE} && apt-get -y autoremove --purge && rm -fr /etc/condor"
			EOF
			;;
		centos|rhel|amzn)
			PACKAGE=condor
			if [ -z "${ROLE}" ]; then
				PACKAGE=minicondor
			fi

			cat >&2 <<-EOF
				To update: $sh_c "yum update ${PACKAGE}"
				To remove: $sh_c "yum -y autoremove ${PACKAGE} && rm -fr /etc/condor"
			EOF
			;;
		esac
		exit 1
	fi

	if [ -z "${ROLE}" ]; then
		echo -e "\n# Installing mini HTCondor for ${lsb_dist^} $dist_version"
	else
		echo -e "\n# Installing HTCondor as ${ROLE} for ${lsb_dist^} $dist_version"
	fi

	# Run install binaries for each distro accordingly
	case "$lsb_dist" in
		ubuntu|debian)
			(
				if ! is_dry_run; then
					set -x
				fi

				echo -e "\n# Adding our repository"

				# Install our repository key.
				$sh_c "apt-get update"
				$sh_c "apt-get install gnupg"
				$sh_c "curl -fsSL ${DOWNLOAD_URL}/debian/HTCondor-Release.gpg.key | apt-key add -"

				# Add our repository.
				HTCONDOR_LIST=/etc/apt/sources.list.d/htcondor.list
				$sh_c "echo \"deb [arch=amd64] ${DOWNLOAD_URL}/repo/${lsb_dist}/${CHANNEL_DIR} ${dist_version} main\" > ${HTCONDOR_LIST}"
				$sh_c "echo \"deb-src ${DOWNLOAD_URL}/repo/${lsb_dist}/${CHANNEL_DIR} ${dist_version} main\" >> ${HTCONDOR_LIST}"

				echo -e "\n# Updating package lists"
				$sh_c "apt-get update"

				# In the Ubuntu 20.04 Docker container, the following packages
				# need to be installed non-interactively, because the pipe
				# out of curl eats the tty they'd like to use:
				#	keyboard-configuration
				#	console-setup
				#	tzdata
				# apt-get will succeed if these packages are already installed,
				# but for now let's not risk screwing things up by adding them
				# on the platforms that don't need them.
				if [[ $lsb_dist = "ubuntu" && $dist_version = "focal" ]]; then
					echo -e "\n# Preconfiguring packages for Ubuntu 20.04."
					$sh_c "DEBIAN_FRONTEND=noninteractive apt-get install -y keyboard-configuration console-setup tzdata"
				fi

				echo -e "\n# Installing HTCondor binaries and dependencies"
				if [ -z "${ROLE}" ]; then
					$sh_c "apt-get install -y minihtcondor"
				else
					$sh_c "apt-get install -y htcondor"
				fi
			)
			;;
		centos|rhel|amzn)
			(
			if ! is_dry_run; then
				set -x
			fi

			repo_dist=${lsb_dist}
			if [ "${repo_dist}" = "centos" ]; then
				repo_dist="rhel"
			fi
			if [ "${repo_dist}" = "rhel" ]; then
				repo_dist="el"
			fi
			repo_suffix="${repo_dist}${dist_version}"
			yum_repo_rpm="${DOWNLOAD_URL}/repo/${CHANNEL}/htcondor-release-current.${repo_suffix}.noarch.rpm"

			echo -e "\n# Installing EPEL"
			epel_version="${dist_version}"
			if [ "${lsb_dist}" = "amzn" ]; then
				if [ "${dist_version}" -eq 2 ]; then
					epel_version="7"
				fi
			fi
			$sh_c "yum install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-${epel_version}.noarch.rpm || yum reinstall -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-${epel_version}.noarch.rpm"

			if [ "${lsb_dist}" != "amzn" ]; then
				#
				# Just to make our lives harder, the packages we need from
				# EPEL depend on repositories which are disabled by default,
				# and those repositories and how to enable them between
				# RHEL and CentOS and between version 7 and 8.
				#
				if [ "${lsb_dist}" = "centos" ]; then
					if [ "${dist_version}" -eq 8 ]; then
						$sh_c "dnf config-manager --set-enabled PowerTools"
					fi
				elif [ "${lsb_dist}" = "rhel" ]; then
					# subscription-manager only works if you've already
					# "register"ed and "attach"ed the system.
					if [ "${dist_version}" -eq 7 ]; then
						$sh_c "subscription-manager repos --enable \"rhel-*-optional-rpms\" --enable \"rhel-*-extras-rpms\" --enable \"rhel-ha-for-rhel-*-server-rpms\""
					elif [ "${dist_version}" -eq 8 ]; then
						ARCH=$(/bin/arch)
						$sh_c "subscription-manager repos --enable \"codeready-builder-for-rhel-8-${ARCH}-rpms\""
					fi
				fi
			fi

			echo -e "\n# Installing the HTCondor repo"
			$sh_c "yum install -y ${yum_repo_rpm} || yum reinstall -y ${yum_repo_rpm}"

			echo -e "\n# Installing HTCondor"
			if [ -z "${ROLE}" ]; then
				$sh_c "yum install ${YUM_REPO} -y minicondor"
			else
				$sh_c "yum install ${YUM_REPO} -y condor"
			fi
			)
			;;
		*)
			if [ -z "$lsb_dist" ]; then
				if is_darwin; then
					echo
					echo "Error: Unsupported operating system 'macOS'"
					echo
					exit 1
				fi
			fi
			echo
			echo "Error: Unsupported distribution '$lsb_dist'"
			echo
			exit 1
			;;
	esac

	if [ -n "${SHARED_FS_DOMAIN}" ]; then
		DOMAIN_CONFIG=$(cat <<-EOF

			# HTCondor assumes that any two machines with the same value
			# for this variable have the same shared filesystems.  Shared
			# filesystems tend not to scale as well as you would like, but
			# they do make it simpler to explain how a job accesses its file.
			FILESYSTEM_DOMAIN = ${SHARED_FS_DOMAIN}

			# If your jobs are accessing a shared filesystem, they probably
			# need to be run as the user who submitted them (as opposed to
			# user nobody or a local user which only runs batch jobs).  This
			# variable must be set to the same thing on the submit machine
			# and on the execute machine to do this.
			UID_DOMAIN = ${SHARED_FS_DOMAIN}

			# The UID_DOMAIN must normally be a suffix of the fully-qualified
			# DNS name of the submit machine (as determined by a reverse
			# lookup of the IP address used to contact the execute machine).
			# Setting this variable relaxes that requirement, which is safe
			# to do for this configuration because only submit machines you
			# trust can contact your execute machines.
			TRUST_UID_DOMAIN = TRUE

			# Normally, before running a job as a particular user (that is,
			# not as user nobody), HTCondor checks to make sure that user
			# is in the password file.  Not all methods for sharing UIDs
			# across machines store every user in every password file (for
			# example, LDAP does not).  Setting this this variable relaxes
			# this requirement.
			SOFT_UID_DOMAIN = TRUE

		EOF
		)
	fi

	# Configure the role, if any.
	echo -e "\n# Configuring role, if any ..."
	case "${ROLE}" in
		"central manager")
			CONFIG_FILE="/etc/condor/config.d/01-central-manager.config"
			$sh_c "echo CONDOR_HOST = ${CONDOR_HOST} > ${CONFIG_FILE}"
			$sh_c "echo '# For details, run condor_config_val use role:get_htcondor_central_manager' >> ${CONFIG_FILE}"
			$sh_c "echo 'use role:get_htcondor_central_manager' >> ${CONFIG_FILE}"

			do_token_security
			;;

		"submit")
			CONFIG_FILE="/etc/condor/config.d/01-submit.config"
			$sh_c "echo CONDOR_HOST = ${CONDOR_HOST} > ${CONFIG_FILE}"
			$sh_c "echo '# For details, run condor_config_val use role:get_htcondor_submit' >> ${CONFIG_FILE}"
			$sh_c "echo 'use role:get_htcondor_submit' >> ${CONFIG_FILE}"

			if [ -n "${SHARED_FS_DOMAIN}" ]; then
				$sh_c "echo '${DOMAIN_CONFIG}' >> ${CONFIG_FILE}"
			fi

			do_token_security
			;;

		"execute")
			CONFIG_FILE="/etc/condor/config.d/01-execute.config"
			$sh_c "echo CONDOR_HOST = ${CONDOR_HOST} > ${CONFIG_FILE}"
			$sh_c "echo '# For details, run condor_config_val use role:get_htcondor_execute' >> ${CONFIG_FILE}"
			$sh_c "echo 'use role:get_htcondor_execute' >> ${CONFIG_FILE}"

			if [ -n "${SHARED_FS_DOMAIN}" ]; then
				$sh_c "echo '${DOMAIN_CONFIG}' >> ${CONFIG_FILE}"
			fi

			do_token_security
			;;

		*)
			# The mini[ht]condor package has done everything for us.
			;;

	esac

	# Open port 9618 on system firewall
	do_configure_firewall

	# Finally, start the HTCondor service
	do_start_service

	echo
}

do_download() {
	get_distribution

	TARBALL_BASE_URL=${DOWNLOAD_URL}/tarball
	TARBALL_DIR_URL=${TARBALL_BASE_URL}/${CHANNEL_DIR}/current

	OS_VERSION=$dist_version
	case "$lsb_dist" in
		# We don't release HTCondor for Fedora.
		# fedora)
		#	;;
		ubuntu)
			OS_NAME=Ubuntu
			dist_version_number="$(. /etc/os-release && echo "$VERSION_ID")"
			dist_version_number=`echo $dist_version_number | sed -e s'/\.[0-9]*//'`
			OS_VERSION=$dist_version_number
			;;
		debian)
			OS_NAME=Debian
			OS_VERSION=$dist_version_number
			;;
		centos|rhel)
			OS_NAME=CentOS
			;;
		amzn)
			OS_NAME=AmazonLinux
			;;
		*)
			if is_darwin; then
				OS_NAME=MacOSX
				OS_VERSION=
			else
				echo
				echo "Error: Unsupported distribution '$lsb_dist'"
				echo
				exit 1
			fi
			;;
	esac

	TARBALL_NAME=condor-x86_64_${OS_NAME}${OS_VERSION}-stripped.tar.gz
	TARBALL_URL=${TARBALL_DIR_URL}/${TARBALL_NAME}

	echo "Downloading to condor.tar.gz..."
	curl -fSL ${TARBALL_URL} -o condor.tar.gz
}

# Set global defaults
CHANNEL="current"
DOWNLOAD_URL="https://research.cs.wisc.edu/htcondor"
DRY_RUN=1
sh_c='sh -c'
unset DIST
PASSWORD=${GET_HTCONDOR_PASSWORD}

# Process command-line options
while [ $# -gt 0 ]; do
	case "$1" in
		--password)
			PASSWORD=$2
			if [ -z "${PASSWORD}" ]; then
				echo "--password requires an argument"
				exit 1
			fi
			shift
			;;
		--shared-filesystem-domain)
			SHARED_FS_DOMAIN=$2
			if [ -z "${SHARED_FS_DOMAIN}" ]; then
				echo "--shared-filesystem-domain requires an argument"
				exit 1
			fi
			shift
			;;
		--minicondor)
			unset ROLE
			;;
		--central-manager)
			ROLE="central manager"
			CONDOR_HOST=$2
			if [ -z "${CONDOR_HOST}" ]; then
				echo "--central-manager requires its external name as an argument"
				exit 1
			fi
			shift
			;;
		--submit)
			ROLE="submit"
			CONDOR_HOST=$2
			if [ -z "${CONDOR_HOST}" ]; then
				echo "--submit requires the central manager as an argument"
				exit 1
			fi
			shift
			;;
		--execute)
			ROLE="execute"
			CONDOR_HOST=$2
			if [ -z "${CONDOR_HOST}" ]; then
				echo "--execute requires the central manager as an argument"
				exit 1
			fi
			shift
			;;
		--yum-repo)
			REPO_SUFFIX=$2
			if [ -z "${REPO_SUFFIX}" ]; then
				echo "--yum-repo requires a repo-suffix as an argument"
				exit 1
			fi
			YUM_REPO=--enablerepo=htcondor-${REPO_SUFFIX}
			shift
			;;
		--channel)
			case "$2" in
				stable)
					CHANNEL="stable"
					;;
				current|latest|developer)
					CHANNEL="current"
					;;
				*-rc)
					CHANNEL="$2"
					;;
				*)
					echo "Illegal option $2 for --channel"
					echo "Run $0 --help for usage"
					exit 1
					;;
			esac
			shift
			;;
		--no-dry-run)
			unset DRY_RUN
			;;
		--dry-run)
			DRY_RUN=1
			;;
		--dist)
			DIST=1
			;;
		--download)
			DOWNLOAD=1
			;;
		--help|-h)
			usage
			;;
		*)
			echo "Illegal option $1"
			echo "Run $0 --help for usage"
			exit 1
			;;
	esac
	shift $(( $# > 0 ? 1 : 0 ))
done

# We have to update this once every stable release; we don't want
# to update a symlink, like we do for 'current', because we don't
# want to surprise other stable-channel users with new features.
if [ "${CHANNEL}" = "stable" ]; then
	CHANNEL_DIR=9.0
else
	CHANNEL_DIR=${CHANNEL}
fi

# wrapped up in functions so that we have some protection against only getting
# half the file during "curl | sh"

if is_download; then
	do_download
else
	do_install
fi

exit 0
