#!/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() {
	set +o pipefail

	# Generates a random password of 32 characters. Trying to avoid URL
	# escaped chars.
	< /dev/urandom tr -dc '!$~()*,.:<=>?[]^_|A-Za-z0-9' | head -c "${1-32}"
	# Dangerous punctuation ignored (for BASH, Python format or URL
	# encoding: "'%&\+;@\`

	set -o pipefail
}

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.
#

# Reachable address for cornac service from Postgres hosts.
CANONICAL_URL = 'https://${fqdn}'

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

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

# Outgoing mail settings.
#MAIL_DSN = 'smtp://user:password@host:port'
#MAIL_FROM = 'PostgreSQL DBaaS server <nobody@acme.tld>'

# Secret for session token signing.
SECRET_KEY = '$(pwgen 128)'

# URL to temBoard UI, without credentials. Set credentials in
cornac/worker/cornac.py file.
TEMBOARD = None

# Make tenant hash uniques for this cornac instance.
TENANT_HASH_SALT = '$(pwgen 16)'
EOF

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

endpoint=$(sudo -u cornac-web /opt/cornac/bin/cornac-shell cornac --verbose guess-endpoint-address)
cat >> $CORNAC_WEB_CONFIG <<EOF

# URI to cornac own Postgres instance.
SQLALCHEMY_DATABASE_URI = f"postgresql://cornac:$(pwgen)@${endpoint}/cornac"
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 ${NGINX_UPSTREAM-127.0.0.1:8001};
}

server {
	listen 443 ssl http2;
	server_name ${fqdn%%.*} ${fqdn} rds.${fqdn};
	ssl_certificate "${sslcert}";
	ssl_certificate_key "${sslkey}";

	access_log /var/log/nginx/cornac.log;
	error_log /var/log/nginx/cornac-error.log${DEBUG+ debug};

	location = / {
		# Redirect to console.
		if (\$request_method = GET) {
			return 301 /cornac/;
		}
		# Or expose RDS API. For compatibility with boto2 as used by Ansible.
		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 Forwarded "for=\$remote_addr";
		proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
		proxy_set_header X-Forwarded-Path /;
		proxy_pass http://cornac/rds/;
	}

	location ~ ^/(iam|rds|sts)/$ {
		proxy_set_header Host \$host;
		# Tell cornac that the RDS client signed the request with
		# another path.
		proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
		proxy_pass http://cornac;
	}

	location /cornac {
		proxy_set_header Host \$host;
		proxy_pass http://cornac;
	}

	location ~ \.(css|eot|ico|js|map|svg|ttf|woff2?) {
		root "${CONSOLE_HTDOCS-/usr/share/aws-console/htdocs}";
	}
}
EOF
# Allow nginx to connect to backend.
if type -p setsebool &>/dev/null && getenforce | grep -qv Disabled ; then
	log "Configuring nginx security policy..."
	setsebool -P httpd_can_network_connect on
	semanage fcontext -a -t httpd_sys_content_t "/usr/share/aws-console/htdocs(/.*)"
	restorecon -v /usr/share/aws-console/
fi
systemctl enable --now 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).
#

# Backup server location.
#
#BACKUPS_LOCATION = 'ssh://user@host/path'

# 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_A = 'datacenter1/datastore/datastore1'
# STORAGE_POOL_B = 'datacenter2/datastore/datastore1'
# VCENTER_RESOURCE_POOL_A = 'datacenter1/host/host1/Resources'
# VCENTER_RESOURCE_POOL_B = 'datacenter2/host/host1/Resources'

# Choose here which operator implementation to use.
OPERATOR = 'basic'

# temBoard admin access for instance registration.
#TEMBOARD_CREDENTIALS = '<user>:<password>'
EOF

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

log "Reloading systemd."
systemctl daemon-reload

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