#!/bin/bash
#
# Bootstrap script called by servermill during cloud server setup. Not
# intended to be run by end users.
#
# To match servermill's behavior, this script expects the environment
# variables USERNAME, DATACENTER, and API_KEY, which are passed to the
# corresponding arguments of driveclient --configure. AUTH_TOKEN may
# substitute for API_KEY if desired.
#
# For testing purposes, you may also supply any of the following. Note
# these are not Officially Supported and should not be relied on in
# production:
#
# CBU_AGENTREPO=file://path/to/repo
# FLAVOR=driveclient-flavor-argument
# CBU_EXTRA_ARGS="--arg1 --arg2"

# FIXME: for debugging, remove in release
set -e
set -u
# Since these are either-or, provide an empty default so set -u doesn't
# trip on whichever one you're not using.
API_KEY=${API_KEY:-}
AUTH_TOKEN=${AUTH_TOKEN:-}
CBAU_EXTRA_ARGS=${CBAU_EXTRA_ARGS:-}

export DEBIAN_FRONTEND=noninteractive  # shut up, apt
# https://manpages.debian.org/bullseye/debconf-doc/debconf.7.en.html#Frontends

pkgcmd () {
	for cmd in apt-get apt yum; do
		command -v $cmd 2>/dev/null && return
	done
}

installdeps () {
	local cmd
	cmd=$(pkgcmd)
	if [ -z "${cmd}" ]; then
		echo "failed to install missing sysdeps (can't find package manager)"
		return 1
	fi
	echo "installing missing sysdeps: $*"
	if [[ $cmd  == */apt* ]]; then
		$cmd update
	fi
	$cmd install -y "$@"
}

geturl () {
	# A few images don't have curl out of the box. This wrapper falls
	# back to wget in those cases. Expects to receive a URL to download
	# and a file to download to. If no file is provided, results go to
	# stdout.
	#
	# At the moment this is only used to download the standalone
	# updater. It might be simpler to pipe it to python rather than
	# saving it, though I'm not sure how to handle errors properly that
	# way.
	local url="${1}"
	local tgt="${2}"
	if command -v curl 2>/dev/null; then
		# FIXME: the redirect here stops curl from inserting a bare carriage
		# return in the log. I am not sure if this hides legitimate errors
		# -- those *should* be on stderr, but verify.
		curl "${url}" \
			--output "${tgt}" \
			--silent \
			--show-error \
			--fail \
			> /dev/null || return
	elif command -v wget 2>/dev/null; then
		wget "${url}" \
			--output-document "${tgt}" \
			--no-verbose \
			|| return
	else
		fail "curl/wget not available"
	fi
}

bootstrap_updater () {
	# Allow overriding the repo path for local testing purposes
	local repo="${CBU_AGENTREPO:-https://agentrepo.${DATACENTER}.cbu.rackspace.net/repo}"
	local script=cbu-updater-standalone.py
	local url=${repo}/${script}
	local path=${TMP}/${script}

	# Simplest way of getting a useful indicator of a preinstalled version
	type cbu-updater >/dev/null 2>&1 && cbu-updater install cbu-updater && return 0
	echo "downloading standalone installer from ${url} to ${path}"
	geturl "${url}" "${path}" || return
	# shellcheck disable=SC2086 # intentional, but should it be?
	python3 "${path}" install cbu-updater ${CBAU_EXTRA_ARGS} || return
}

kicksvc () {
	local svc="$1"
	systemctl restart "${svc}" ||
		service "${svc}" restart ||
		/etc/init.d/"${svc}" restart
}

register () {
	local autharg
	local authval
	local conf=/etc/driveclient/bootstrap.json
	local req_vars="USERNAME DATACENTER"
	local missing_vars=""
	for var in ${req_vars}; do
		[ -z "${!var}" ] && missing_vars="${missing_vars}${var} "
	done
	if [ -z "${API_KEY}${AUTH_TOKEN}" ]; then
		missing_vars="${missing_vars}(API_KEY|AUTH_TOKEN) "
	fi
	if [ -n "${missing_vars}" ]; then
		fail "can't register; missing servermill env vars: ${missing_vars}"
	fi

	if [ -f ${conf} ]; then
		echo "skipping agent configuration (agent already configured)"
		return 0
	fi

	if [ -n "${AUTH_TOKEN}" ]; then
		autharg=authtoken
		authval=${AUTH_TOKEN}
	elif [ -n "${API_KEY}" ]; then
		autharg=apikey
		authval=${API_KEY}
	else
		# shouldn't happen, precheck should catch this
		echo "must supply at least one of AUTH_TOKEN or API_KEY"
		return 1
	fi

	echo "configuring driveclient for ${USERNAME} in ${DATACENTER}"
	driveclient \
		--configure \
		--noninteractive \
		--flavor "${FLAVOR:-raxcloudserver}" \
		--datacenter "${DATACENTER}" \
		--user "${USERNAME}" \
		--${autharg} "${authval}" \
		|| return
		# --apihost api.${DATACENTER}.cbu.rackspace.net
		# may not need to specify apihost; it should get the right one
		# from the service catalog, I think?
	kicksvc driveclient > /dev/null 2>&1
}

fail () {
	# Shorthand to echo a message and then exit with an error
	local msg=${1}
	echo "${msg}"
	exit 1;
}

main () {
	# We may be migrating from the legacy updater, or installing from
	# scratch. These are `migrate` and `bootstrap` modes respectively.
	# While they can be specified explicitly for testing purposes, in
	# prod this script gets called by either servermill or the legacy
	# updater with arguments we don't control.
	#
	# We can detect which one called us by examining $0, the name it
	# called us by. Servermill looks for this script under the name
	# `cbu-installer`, and pipes that to sh; either name should be
	# interpreted as bootstrap mode. For legacy updates, we are
	# hijacking the legacy updater's `installstartup.sh` file; that name
	# is thus interpreted as migration mode.
	#
	# For testing purposes, either mode may be forced by specifying it
	# as the first argument.
	local runmode
	case $(basename "${0}") in
		installstartup.sh ) runmode=migrate   ;;
		cbu-installer     ) runmode=bootstrap ;;
		bash              ) runmode=bootstrap ;;
		sh                ) runmode=bootstrap ;;
		*                 ) runmode=bootstrap ;;
	esac
	case ${1:-} in
		bootstrap )   runmode=bootstrap  ;;
		migrate   )   runmode=migrate    ;;
	esac
	case $runmode in
		bootstrap ) echo "initiating cbu agent bootstrap" ;;
		migrate   ) echo "migrating cbu legacy updater" ;;
	esac

	if [ "$EUID" -ne 0 ]; then
		local lines=("WARNING: we are not root. This is allowed for"
		             "testing purposes, but you should expect driveclient"
		             "--configure to fail")
		echo "${lines[*]}"
	fi

	type python3 > /dev/null || installdeps python3 ||
		fail "error installing python3 dependency, aborting"
	bootstrap_updater ||
		fail "couldn't bootstrap updater, aborting"
	cbu-updater install driveclient ||
		fail "failed to install driveclient, aborting"

	if [ $runmode = migrate ]; then
		# migrations can stop here, they don't need to register
		return 0
	fi

	if register; then
		:  # noop
	elif [[ $? -eq 139 ]]; then
		echo "CBU-1003 segfault bug detected; attempting nscd workaround"
		installdeps nscd || fail "problem installing nscd (returned $?)"
		echo "retrying driveclient configuration"
		register || fail "agent configuration still failed; aborting"
	else
		fail "failed to install driveclient, aborting"
	fi
}

# Haven't found a way around the global here...
TMP="$(mktemp -d)" || fail "Failed to create temporary directory"
trap 'rm -rvf -- "$TMP"' EXIT
main "$@" >&2
