#!/bin/bash -eu

set -o pipefail
shopt -s expand_aliases

# Setup logging. If DEBUG env is set, output everything to stderr. Else, keep
# tty opened in FD 3, redirect stdout to stderr, and stderr to $LOGFILE. This
# way, everything is catched in LOGFILE, message to user must be sent to FD 3
# with log() or fatal().
if [ -n "${DEBUG-}" ] ; then
	exec 3>/dev/null
	# On debug mode, just alias log to :, set -x shows messages in stderr.
	alias log=:
else
	LOGFILE=/var/log/cornac-setup.log
	exec 3>&2 2>${LOGFILE} 1>&2
	chmod 0600 ${LOGFILE}
	trap 'catchall' INT EXIT TERM
	log() {
		echo "$@" >&3
	}
fi

catchall() {
	if [ $? -gt 0 ] ; then
		fatal "Failure. See ${LOGFILE} for details."
	else
		rm -f ${LOGFILE}
	fi
	trap - INT EXIT TERM
}

create_user() {
	local name=$1; shift
	local groups="$@"

	local home=/etc/opt/cornac/${name#*-}
	if ! getent passwd $name ; then
		log "Creating system user $name."
		useradd \
			--system --user-group --shell /sbin/nologin \
			--home-dir $home \
			$name &>/dev/null
	fi

	install --directory --mode 0750 --owner $name --group $name $home
	install --no-target-directory --owner $name --group $name --mode 0644 bashrc $home/.bashrc
	if [ -n "${groups[*]}" ] ; then
		usermod --append --groups "${groups[@]}" $name
	fi
}

fatal() {
	echo -e "\e[1;31m$@\e[0m" >&3
	exit 1
}

guess_fqdn() {
	local fqdn=$(hostname --fqdn)
	if [ -n "${fqdn##*.*}" ] ; then
		# FQDN is not configured. We only have hostname.
		hostname=$fqdn
		for ip in $(hostname --all-ip-addresses) ; do
			if fqdn=$(getent hosts $ip | grep -Po "$ip\s+\K(${hostname}\..*)") ; then
				break
			fi
			fqdn=
		done
	fi

	if [ -n "${fqdn##*.*}" ] ; then
		return 1
	fi

	echo "${fqdn}"
}

pwgen() {
	# Generates a random password of 32 hexadecimal characters.
	od -vN $((${1-32} / 2)) -An -tx1 /dev/urandom | tr -d ' \n'
}

sslgen() {  # <certpath> <keypath>
	local cert=$1; shift
	local key=$1; shift

	openssl req -new -x509 -days 365 -nodes \
		-subj "/C=XX/ST= /L=Default/O=Default/OU= /CN= " \
		-out $cert -keyout $key
}

write_config() {  # <owner> <path> [mode]
	# File content is read from stdin.

	local owner=$1; shift
	local path=$1; shift
	local mode=${1-0600}
	log "Writing configuration in ${path}."
	touch $path
	chown $owner: $path
	chmod $mode $path
	cat >$path
}

# Now, log everything. This is very verbose.
set -x


#       M A I N

# Move to datadir so that other files are in current directory.
cd $(readlink -m ${BASH_SOURCE[0]}/..)

if ! locale -a | grep -q en_US.utf8 ; then
	fatal "Cornac requires missing en_US.utf8 locale."
fi

log "Setting up journald."
# This triggers on disk logging.
install --directory --mode 0755 --owner root --group root /var/log/journal

mkdir -p /etc/opt/cornac

if ! fqdn=$(guess_fqdn) ; then
	fatal "Can't guess FQDN."
fi
domain="${fqdn#*.}"
log "Detected DNS domain .${domain} ."

#       C O R N A C   W E B

create_user cornac-web

CORNAC_WEB_CONFIG=~cornac-web/cornac.py
write_config cornac-web $CORNAC_WEB_CONFIG 0640 <<EOF
#
#        C O R N A C   W E B   C O N F I G U R A T I O N
#
# Generated by $0 at $(date).
#
# This file is inherited by cornac worker configuration.
#

# AWS credentials. See cornac generate-credentials to assist credentials
# setting.
CREDENTIALS = {}

# The DNS domain to build Postgres host resolvable FQDN.
DNS_DOMAIN = '.${domain}'

# The prefix of all Postgres VM.
MACHINE_PREFIX = 'cornac-'

# URI to cornac own Postgres instance, build from options above.
SQLALCHEMY_DATABASE_URI = f"postgresql://cornac:$(pwgen)@{MACHINE_PREFIX}cornac{DNS_DOMAIN}/cornac"
EOF

write_config cornac-web ~cornac-web/environment.conf <<EOF
CORNAC_CONFIG=${CORNAC_WEB_CONFIG}
EOF

sslcert=/etc/pki/tls/certs/cornac.pem
sslkey=/etc/pki/tls/private/cornac.key
if [ -f "$sslcert" ] ; then
	log "Reusing SSL certificate ${sslcert}."
else
	log "Generating self-signed certificate ${sslcert}..."
	sslgen $sslcert $sslkey
fi

log "Setting up nginx."
write_config root /etc/nginx/conf.d/cornac.conf 0644 <<EOF
upstream cornac {
	 server 127.0.0.1:5000;
}

server {
	listen 443 ssl http2;
	server_name ${fqdn} rds.${fqdn};
	root "/usr/share/nginx/html";

	ssl_certificate "${sslcert}";
	ssl_certificate_key "${sslkey}";

	location = / {
		proxy_redirect http://\$proxy_host:\$proxy_port/rds /;
		proxy_set_header Host \$host;
		# Tell cornac that the RDS client signed the request with
		# another path.
		proxy_set_header X-Forwarded-Path /;
		proxy_pass http://cornac/rds;
	}
}
EOF
# Allow nginx to connect to backend.
if type -p setsebool &>/dev/null && getenforce | grep -qv Disabled ; then
	setsebool httpd_can_network_connect on
fi
systemctl enable nginx
systemctl start nginx
systemctl reload nginx

#       C O R N A C   W O R K E R

create_user cornac-worker cornac-web

if ! [ -f ~cornac-worker/.ssh/id_rsa ] ; then
	log "Generating SSH private key for cornac-worker."
	sudo -nu cornac-worker ssh-keygen -q -b 4096 -t rsa -f ~cornac-worker/.ssh/id_rsa -N ""
fi

write_config cornac-worker ~cornac-worker/.ssh/config <<EOF
Host *
    AddKeysToAgent yes
EOF

CORNAC_WORKER_CONFIG=~cornac-worker/cornac.py
write_config cornac-worker $CORNAC_WORKER_CONFIG <<EOF
#
#        C O R N A C   W O R K E R   C O N F I G U R A T I O N
#
# Generated by $0 at $(date).
#

# Configure here IAAS access.
#
# For libvirt:
# IAAS = 'libvirt+'
#
# For vCenter:
# IAAS = 'vcenter+https://user@sso:password@vcenter.lan/'
IAAS = None

#       V C E N T E R
#
# If you run cornac on vCenter, you need to adapt the following options:
#
# NETWORK = 'datacenter1/network/My Network'
# MACHINE_ORIGIN = 'datacenter1/vm/cornac--origin'
# STORAGE_POOL = 'datacenter1/datastore/datastore1'
# VCENTER_RESOURCE_POOL = 'datacenter1/host/host1/Resources'
EOF

write_config cornac-worker ~cornac-worker/environment.conf <<EOF
CORNAC_CONFIG=${CORNAC_WEB_CONFIG},${CORNAC_WORKER_CONFIG}
EOF

log "Reloading systemd."
systemctl daemon-reload

log "Done."
log
log "Cornac is pre-configured. Continue with Installation documentation."
log
