#! /usr/bin/env bash

set -eo pipefail

usage()
{
cat <<USAGE_END
usage:
	$0 [--ca <ca_cert.crt>] [--auth-user <user>] [--auth-host <auth_host>] [--scripts-dir <path>]

	Build an site-specific configuration file using the CA certificate
	provided.  Authorization will connect to the SSH host specified.

parameters:
	--ca <ca_cert.crt>: The CA certificate to install.  Default is the
		CA from ~/pki/ca.crt.  Also used to authenticate the server when
		downloading app manifests.
	--auth-user <user>: The user to use when authorizing over SSH.  If not
		given, default is the user building this package.  The user's
		home directory must contain the PKI installation for this CA.
	--auth-host <auth_host>: The SSH host we will use to authorize the
		certificate.  This must be reachable during package build to
		scan the SSH keys.  Default is the FQDN found in the CA
		certificate.
	--auth-port <auth_port>: The port open for SSH on the auth host.
		Default is detected from /etc/ssh/sshd_config.
	--scripts-dir <path>: Location of the scripts directory (the directory
		containing this file) on the SSH host.  Default is the location
		of this script.  Usually used when building the package off of
		the auth server.  Provide an absolute path since the script
		cannot resolve it.
USAGE_END
}

CA_CERT="$HOME/pki/ca.crt"
AUTH_SSH_USER="$(whoami)"
AUTH_SSH_HOST=
AUTH_SSH_PORT=
SCRIPTS_DIR="/usr/bin"

while [ "$#" -gt 0 ]; do
	case "$1" in
	--help)
		usage
		exit 0
		;;
	--ca)
		CA_CERT="$(realpath "$2")"
		shift 2
		;;
	--auth-user)
		AUTH_SSH_USER="$2"
		shift 2
		;;
	--auth-host)
		AUTH_SSH_HOST="$2"
		shift 2
		;;
	--auth-port)
		AUTH_SSH_PORT="$2"
		shift 2
		;;
	--scripts-dir)
		if [[ "$2" = /* ]]; then
			# Absolute path.  This usually is used when building
			# off-server, so this path might not actually exist on
			# this system.  Don't try to resolve it with realpath.
			SCRIPTS_DIR="$2"
		else
			# Relative path.  We need an absolute path, so the best
			# we can do is try to resolve it on the local system.
			# Print a message since this might not resolve
			# correctly.
			SCRIPTS_DIR="$(realpath "$2")"
			echo "Resolved script directory $2 to $SCRIPTS_DIR" >&2
		fi
		shift 2
		;;
	--)
		shift
		break
		;;
	--*)
		echo "unknown parameter: $1" >&2
		usage
		exit 1
		;;
	*)
		break
		;;
	esac
done

# We need jq
if ! hash jq; then
	echo "Installing jq..."
	sudo apt install -y jq
fi

# CA certificate must end in .crt to work
if [[ "$(basename "$CA_CERT")" != *.crt ]]; then
	echo "CA certificate must have .crt extension" >&2
	exit 1
fi

# Get certificate CN
AWK_GET_CN='/^subject=CN = / { gsub(/^subject=CN = /, ""); print }'
CERT_CN="$(openssl x509 -noout -subject -in "$CA_CERT" | awk "$AWK_GET_CN")"
DOMAIN="${CERT_CN#CA for }"

if [ -z "$AUTH_SSH_HOST" ]; then
	AUTH_SSH_HOST="$DOMAIN"
fi

if [ -z "$AUTH_SSH_PORT" ]; then
	AWK_GET_SSH_PORT='/^Port / { gsub(/^Port /, ""); print }'
	AUTH_SSH_PORT="$(</etc/ssh/sshd_config awk "$AWK_GET_SSH_PORT" | head -1)"
	if [ -z "$AUTH_SSH_PORT" ]; then
		AUTH_SSH_PORT=22
	elif ! [[ $AUTH_SSH_PORT =~ ^[0-9]+$ ]]; then
		echo "Warning: could not understand SSH port $AUTH_SSH_PORT, default to 22" >&2
		AUTH_SSH_PORT=22
	fi
fi

if [ -z "$CLIENT_CERT" ]; then
	CLIENT_CERT=~/pki/issued/"$DOMAIN-client.crt"
fi

if [ -z "$CLIENT_KEY" ]; then
	CLIENT_KEY=~/pki/private/"$DOMAIN-client.key"
fi

# Allow only alphanumerics and - in the file name
CFG_NAME_DIRTY="$DOMAIN-site"
CFG_NAME=
while [ -n "$CFG_NAME_DIRTY" ]; do
	case "${CFG_NAME_DIRTY:0:1}" in
	[a-zA-Z0-9-])
		CFG_NAME+="${CFG_NAME_DIRTY:0:1}"
		;;
	*)
		CFG_NAME+=-
		;;
	esac
	CFG_NAME_DIRTY="${CFG_NAME_DIRTY:1}"
done
CFG_NAME+=".json"

if [ -e "$CFG_NAME" ]; then
	echo "File already exists: $CFG_NAME" >&2
	echo "Delete the file to create again" >&2
	exit 1
fi

# The JSON file contains:
#
# {
#     "ca": "<PEM certificate>",
#     "domain": "<Server FQDN>",
#     "ssh": {
#         "hostkey": "<Server's SSH host key for known_hosts>",
#         "user": "<username>",
#         "host": "<host>",
#         "port": "<port>"
#     },
#     "scripts": "<absolute path to scripts directory on server>"
# }
#
# PWAs must specify a manifest_url even if the manifest JSON is included, this
# is needed to create the web app.
#
# Downloading the manifest from the server is preferred, but Plinth
# (FreedomBox) does not have a manifest.

CFG_JSON="{}"

# Build a JSON object from named name-value pairs.  The values are all strings.
build_json_obj() {
	local objvalue="{}"
	local pn pv
	while [ "$#" -ge 2 ]; do
		pn="$1"
		pv="$2"
		shift 2
		objvalue="$(echo "$objvalue" | jq --arg pn "$pn" --arg pv "$pv" '. + {($pn):$pv}')"
	done

	echo "$objvalue"
}

# Update CFG_JSON by invoking jq with the given arguments.  The input is the old
# CFG_JSON, and the result is captured as the new CFG_JSON.  Usually the filter
# is like '. + {name:value}' to add properties.
jq_update_cfg_json() {
	CFG_JSON="$(echo "$CFG_JSON" | jq "$@")"
}

# Update CFG_JSON by adding a JSON property with the given name and value.  The
# value can be any valid JSON value, including numbers, objects, arrays, etc.
jq_add_cfg_json_value() {
	local name="$1" value="$2"
	# shellcheck disable=SC2016
	jq_update_cfg_json --arg name "$name" --argjson value "$value" '. + {($name):$value}'
}

# Update CFG_JSON by adding a string property with the given name and value.
jq_add_cfg_json_str() {
	local name="$1" value="$2"
	# shellcheck disable=SC2016
	jq_update_cfg_json --arg name "$name" --arg value "$value" '. + {($name):$value}'
}

# Update CFG_JSON by adding an object value to a property with the given name.
# The remaining arguments are all name-value pairs for the object value.  (All
# these values are strings.)
jq_add_cfg_json_obj() {
	local name="$1"
	shift

	jq_add_cfg_json_value "$name" "$(build_json_obj "$@")"
}

# Get SSH host key
AUTH_SSH_HOSTKEY="$(ssh-keyscan -p "$AUTH_SSH_PORT" "$AUTH_SSH_HOST")"

# Add the specified CA certificate
jq_add_cfg_json_str ca "$(cat "$CA_CERT")"

# Add the site domain
jq_add_cfg_json_str domain "$DOMAIN"

# Add the SSH authorization information
jq_add_cfg_json_obj ssh hostkey "$AUTH_SSH_HOSTKEY" user "$AUTH_SSH_USER" \
	host "$AUTH_SSH_HOST" port "$AUTH_SSH_PORT"

# Add the scripts directory path
jq_add_cfg_json_str scripts "$SCRIPTS_DIR"

echo "$CFG_JSON" >"$CFG_NAME"

echo
echo "Built site configuration file:"
echo "$CFG_NAME"
