#!/bin/sh -fu
#
# Copyright (C) 2024  Paul Wolneykien.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

#ICINGA2_DIR=/var/lib/icinga2
#CERTDIR="$ICINGA2_DIR/certs"
ICINGA2_GROUP=icinga

PROG="${0##*/}"
VERSION="0.1.0"

usage()
{
    [ "$1" = 0 ] || exec >&2
    cat <<EOF
Usage: $PROG [options] [<Icinga Web 2 URL> <host-template-key>]

$PROG is a tool to register and set up an Icinga 2 node via
an Icinga 2 Director self-service.

Options:

  --name    optional node name (the default is local hostname);

  --zone    optional local zone name (the default is the node name);

  --address    optional node address (the default is hostname);

  --remote    optional remote endpoint address in the form
              CN[,HOST[,PORT]] (the default is derived from the
              specified Icinga Web 2 URL);

  --parent-zone    name of the parent zone (the default is 'master');

  --trustedcert    optional trusted certificate file to verify
                   the parent node (the default is to download and
                   check it interactively);

  --ticket    optional Icinga 2 ticket to setup the node with
              (if specified, then no Icinga Web 2 URL is used);

  --web-ca    optional CA file to verify the Icinga Web 2 URL with;

  --no-web-ca    skip Icinga Web 2 URL verification;

  -V | --version    print the program version information and exit;

  -h | --help       print this help and exit.
EOF
    exit "${1:-0}"
}

TEMP="$(getopt -n "$PROG" -o hV -l name:,zone:,address:,remote:,parent-zone:,trustedcert:,ticket:,web-ca:,no-web-ca,help,version -- "$@")" || usage 1
eval set -- "$TEMP"

name="$(hostname -s)"
zone=
address=
remote=
parent_zone='master'
trustedcert=
ticket=
base_url=
template_key=
web_ca=
no_web_ca=
while :; do
    case "$1" in
	--name)
	    shift; name="$1"
	    ;;
	--zone)
	    shift; zone="$1"
	    ;;
	--address)
	    shift; address="$1"
	    ;;
	--remote)
	    shift; remote="$1"
	    ;;
	--parent-zone)
	    shift; parent_zone="$1"
	    ;;
	--trustedcert)
	    shift; trustedcert="$1"
	    ;;
	--ticket)
	    shift; ticket="$1"
	    ;;
	--web-ca)
	    shift; web_ca="$1"
	    ;;
	--no-web-ca)
	    no_web_ca=1
	    ;;
        -h|--help)
	    usage 0
            ;;
	-V|--version)
	    cat <<EOF
$VERSION 2024
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
EOF
	    exit 0
	    ;;
        --)
	    shift
	    break
            ;;
        *)
	    message "$PROG: unrecognized option: $1" >&2
	    usage 1
            ;;
    esac
    shift
done

if [ -z "$ticket" ]; then
    if [ $# -eq 0 ]; then
	echo "ERROR: Neither Icinga Web 2 URL nor a ticket is specified." >&2
	exit 1
    elif [ $# -gt 2 ]; then
	echo "Too much arguments." >&2
	usage 1 || exit $?
    else
	base_url="$1"
	template_key="${2:-}"
    fi

    if [ -z "$base_url" ]; then
	echo "ERROR: Base URL should not be empty." >&2
	exit 1
    fi

    if [ -z "$template_key" ]; then
	echo "ERROR: You need to specify the self-service host template key to register with." >&2
	exit 1
    fi
else
    [ $# -eq 0 ] || usage 1 || exit $?
fi

if [ -z "$name" ]; then
    echo "ERROR: Unable to autodetect the node name." >&2
    exit 1
fi

zone="${zone:-$name}"
if [ -z "$zone" ]; then
    echo "ERROR: Unable to autodetect the local zone name." >&2
    exit 1
fi

address="${address:-$(hostname -s)}"
if [ -z "$address" ]; then
    echo "ERROR: Unable to autodetect the node address." >&2
    exit 1
fi

if [ -z "$remote" -a -n "$base_url" ]; then
    remote="${base_url#*://}"
    remote="${remote%%:*}"
    remote="${remote%%/*}"
fi
if [ -z "$remote" ]; then
    echo "ERROR: Unable to autodetect the remote endpoint." >&2
    exit 1
fi

if [ -z "$parent_zone" ]; then
    echo "ERROR: The parent zone name should not be empty." >&2
    exit 1
fi

try_register() {
    curl -sS --fail-with-body \
	 ${web_ca:+--cacert "$web_ca"} \
	 ${no_web_ca:+-k} \
	 -H 'Accept: application/json' -X POST \
	 -d "{ \"display_name\": \"$name\", \"address\": \"$address\" }" \
	 "${base_url%/}/director/self-service/register-host?name=$name&key=$template_key"
}

request_ticket() {
    local token="$1"
    curl -sS --fail-with-body \
	 ${web_ca:+--cacert "$web_ca"} \
	 ${no_web_ca:+-k} \
	 -H 'Accept: application/json' \
	 "${base_url%/}/director/self-service/ticket?key=$token"
}

if [ -z "$ticket" ]; then
    echo "Trying to register as $name ($address) with Icinga 2 Director self-service at $base_url..." >&2
    if ! token="$(try_register)"; then
	ret=$?
	echo "$token" >&2
	exit $ret
    fi

    if [ -z "$token" ]; then
	echo "BUG! The Icinga 2 Director didn't return a token." >&2
	exit 1
    fi

    echo "Okay, successfully registered and got a host token." >&2

    token="${token#\"}"
    token="${token%\"}"

    echo "Trying to get a node ticket via Icinga 2 Director self-service at $base_url..." >&2

    if ! ticket="$(request_ticket "$token")"; then
	ret=$?
	echo "$ticket" >&2
	exit $ret
    fi

    if [ -z "$ticket" ]; then
	echo "BUG! The Icinga 2 Director didn't return a ticket." >&2
	exit 1
    fi

    echo "Okay, successfully got a ticket." >&2
fi

ticket="${ticket#\"}"
ticket="${ticket%\"}"

workdir="$(mktemp -d --tmpdir=/tmp icinga2-register-host.XXXX)"
cleanup()
{
    [ -z "$workdir" ] || rm -rf "$workdir"
}
trap 'cleanup' EXIT

#echo "Generating certificate request for $name..." >&2
#icinga2 pki new-cert --cn "$name" \
#	             --key "$CERTDIR/$name.key" \
#		     --cert "$$CERTDIR/$name.crt"
#echo "Successfully generated CSR." >&2

if [ -z "$trustedcert" ]; then
    echo "A trusted certificate of the parent isn't specified. You need to verify one manually:" >&2
    parentaddr="${remote#*,}"
    parentport=
    if [ "${parentaddr#*,}" != "$parentaddr" ]; then
	parentport="${parentaddr#*,}"
    fi
    parentaddr="${parentaddr%,*}"

    mkdir "$workdir/icinga2"
    chgrp "$ICINGA2_GROUP" "$workdir/icinga2"
    chmod g+wx "$workdir/icinga2"
    chmod o+x "$workdir"

    icinga2 pki save-cert --trustedcert "$workdir/icinga2/trusted.crt" \
	                  --host "$parentaddr" \
                          ${parentport:+--port "$parentport"} || \
	exit $?

    trustedcert="$workdir/icinga2/trusted.crt"
    if [ ! -s "$trustedcert" ]; then
	echo "BUG! No trusted certificate found!" >&2
	exit 1
    else
	echo -n "Is the above certificate valid? (y/N): " >&2
	read -r user_answer
	case "$user_answer" in
	    y|Y|[Yy][Ee][Ss])
		;;
	    *)
		exit 1
		;;
	esac
    fi
fi

echo "Setting up the node as $name in $parent_zone.$zone..." >&2
icinga2 node setup --ticket "$ticket" \
	           --cn "$name" \
		   --endpoint "$remote" \
		   --zone "$zone" \
		   --parent_zone "$parent_zone" \
		   --parent_host "${remote%*,}" \
		   --trustedcert "$trustedcert" \
		   --accept-commands --accept-config
