#!/usr/bin/bash

SECNUM=1
function section { echo -e "\n\\033[1;4m$SECNUM.  $1\\033[0m"; SECNUM=$((SECNUM+1)); };
function item    { echo -e "\n\\033[1m$1\\033[0m"; };

if [ "$EUID" -ne 0 ]; then
        echo "(basename $0): This script must be run as root"
        exit 1
fi

usage() {
	cat 1>&2 <<-EOF
		Usage: $0 Parameters

		Parameters
		   --cluster-name  Friendly name of this application cluster		             (required)
		   --server-id     The ID of this node (e.g. 1)                                      (required)
		   --self          The internal hostanme of this node (e.g. node1.localdomain)       (required)
		   --peer          The internal hostanme of the peer node (e.g. node2.localdomain)   (required)

		e.g. $0 --cluster-name=<saas-cluster> --server-id=<n> --self=<local-internal-hostname> --peer=<peer-internal-hostname>

		EOF
	exit 100
}

for i in "$@"; do
        case $i in
	    --cluster-name=*)
	        CLUSTER_NAME="${i#*=}"
	        shift
		;;
            --server-id=*)
                SERVER_ID="${i#*=}"
                shift
                ;;
            --self=*)
                SELF="${i#*=}"
                shift
                ;;
            --peer=*)
                PEER="${i#*=}"
                shift 
                ;;
            *)
               # unknown option
               echo "ERROR: Unrecognized parameter $i"
               usage  
               ;;
        esac
done

if [[ -z "$CLUSTER_NAME" || -z "$SERVER_ID" || -z "$SELF" || -z "$PEER" ]]; then
        usage
        exit 1
fi

#
# Create Certificates and Keys
#
BASEDATE="2024-12-01 00:00:00"
BASEDIR="/root/mysql-cluster/X.509"
CADIR="$BASEDIR/$CLUSTER_NAME"

item " * The CA directory is $CADIR"
section "Creating certificates"

if [[ ! -d $CADIR ]]; then

	echo " - Creating $CADIR"
	mkdir -p $CADIR/{certs,crls,keys,csrs,keys+crts,db}
	mkdir -p $CADIR/certs/byserial

	echo " - Generating config ($CADIR/db/openssl.cnf)"
	cat <<-EOD >$CADIR/db/openssl.cnf
		[ ca ]
		default_ca      = CA_default            # The default ca section

		[ CA_default ]
		dir            = $CADIR                 # top dir
		database       = \$dir/db/index.txt     # index file.
		new_certs_dir  = \$dir/certs/byserial   # new certs dir

		certificate    = \$dir/certs/mysql-CA.crt     # The CA cert
		serial         = \$dir/db/serial              # serial no file
		private_key    = \$dir/keys/mysql-CA.key      # CA private key
		RANDFILE       = \$dir/db/.rand               # random number file

		default_days      = 7300               # how long to certify for (20 years)
		default_startdate = 20241201000000Z    # For repeatability, these are 20 years certs anyway
		default_crl_days  = 30                 # how long before next CRL
		default_md        = sha256             # md to use

		policy          = policy_any           # default policy
		email_in_dn     = yes                  # Don't add the email into cert DN

		name_opt        = ca_default           # Subject name display option
		cert_opt        = ca_default           # Certificate display option
		copy_extensions = none                 # Don't copy extensions from request

		[ policy_any ]
		countryName            = supplied
		stateOrProvinceName    = optional
		organizationName       = optional
		organizationalUnitName = optional
		commonName             = supplied
		emailAddress           = optional

		[ req ]
		dir                    = $CADIR
		default_keyfile        = \$dir/keys/mysql-CA.key
		default_bits           = 4096
		default_md             = sha256
		x509_extensions        = v3_ca
		prompt                 = no
		distinguished_name     = root_ca_DN

		[ root_ca_DN ]
		commonName = $DOMAIN MySQL CA
		countryName = AU
		stateOrProvinceName = New South Wales
		localityName = Sydney
		0.organizationName = Noggin Pty Ltd
		emailAddress = sysadmin@noggin.com.au

		[ v3_ca ]
		# PKIX recommendation.
		subjectKeyIdentifier=hash
		authorityKeyIdentifier=keyid:always,issuer:always
		basicConstraints = CA:true

		[ v3_usr ]
		subjectKeyIdentifier=hash
		basicConstraints = critical,CA:false
		keyUsage = critical,digitalSignature,nonRepudiation,keyEncipherment,dataEncipherment
		EOD

	echo '01' >$CADIR/db/serial
	touch $CADIR/db/index.txt

	section "Generating CA Cert"

	# Generate the CA private key
	ng-mk-rsa-key -f /etc/sysconfig/authseed "mysql::${DOMAIN}:CA" 4096 >$CADIR/keys/mysql-CA.key

	# Now generate the CA's root certificate (30 year expiry)
	env TZ=UTC faketime -f "${BASEDATE}" openssl req -new -x509 -utf8 -batch -config $CADIR/db/openssl.cnf \
		-key $CADIR/keys/mysql-CA.key \
		-days $[30*365]  -outform PEM -out $CADIR/certs/mysql-CA.crt \
		2>&1 | sed -ure 's/^/   /g'
else
	if [[ ! -d "$CADIR/certs" ]] || [[ ! -f "$CADIR/certs/mysql-CA.crt" ]]; then
		echo "$CADIR already exists but is missing expected files, it must be removed before this script is run"
		exit 2
	fi
fi

# Now generate the CSRs
section "Generating CSRs"
for ACCOUNT in $SELF $PEER; do
	FN="mysql-${ACCOUNT//.*}"
	item " * $FN ($ACCOUNT)"
	# Skip existing hosts
	if [ -f $CADIR/certs/$FN.crt ]; then
	  echo "  Certificate for $ACCOUNT already exists, skipping"
	  continue
	fi

	# Generate the account private key
  	ng-mk-rsa-key -f /etc/sysconfig/authseed "mysql::${DOMAIN}:${ACCOUNT}" 2048 >$CADIR/keys/$FN.key

	openssl req -new -utf8 -batch \
	  -key $CADIR/keys/$FN.key \
	  -subj "/C=AU/ST=New South Wales/O=Noggin Pty Ltd/CN=$ACCOUNT" \
	  -outform PEM -out $CADIR/csrs/$FN.csr 2>&1 | sed -ure 's/^/   /g'
done

# And sign them
section "Signing CSRs"
for ACCOUNT in $SELF $PEER; do
	FN="mysql-${ACCOUNT//.*}"
	item " * $FN ($ACCOUNT)"
	# Skip existing hosts
	if [ -f $CADIR/certs/$FN.crt ]; then
		echo "  Certificate for $ACCOUNT already exists, skipping"
		continue
	fi
	env TZ=UTC faketime -f "${BASEDATE}" openssl ca -batch -utf8 -config $CADIR/db/openssl.cnf \
	   -in  $CADIR/csrs/$FN.csr \
	  -out $CADIR/certs/$FN.crt -notext 2>&1  | sed -ure 's/^/   /g'
done

section "Creating PEM KEY+CRT bundles"
for i in $CADIR/certs/*.crt; do
	NAME=$(basename $i)
	NAME=${NAME%%.crt}
	item " * $NAME"
	echo "   cat $CADIR/keys/$NAME.key $i >$CADIR/pems/$NAME.pem"
	cat $CADIR/keys/$NAME.key $i >$CADIR/keys+crts/$NAME.pem
done



#
# Join MySQL Cluster
#
# Make sure the config files exist
section "Installing Certifcates"
FN="mysql-${SELF//.*}"
item " * Installing certificate files"
install -b -v -m 0644 $CADIR/certs/mysql-CA.crt      /etc/pki/tls/certs/mysql.ca-bundle | sed -re 's/^/   /g'
install -b -v -m 0644 $CADIR/certs/${FN}.crt         /etc/pki/tls/certs/                | sed -re 's/^/   /g'
install -b -v -m 0640 -g mysql $CADIR/keys/${FN}.key /etc/pki/tls/private/              | sed -re 's/^/   /g'

# Make sure the config files exist
item " * Updating mysql configuration"
touch -a /etc/mysql/conf.d/60-ssl.cnf /etc/mysql/conf.d/70-replication.cnf

item " * Updating mysql configuration files /root/.my.cnf, /etc/mysql/conf.d/60-ssl.cnf and /etc/mysql/conf.d/70-replication.cnf"
# Use augeas to update them
cat <<-EOD | augtool --autosave
	set /augeas/load/MyCnf/lens MySQL.lns
	set /augeas/load/MyCnf/incl /root/.my.cnf
	load

	set /files/etc/mysql/conf.d/60-ssl.cnf/target[.='mysqld'] 'mysqld'
	set /files/etc/mysql/conf.d/60-ssl.cnf/target[.='mysqld']/ssl-ca   /etc/pki/tls/certs/mysql.ca-bundle
	set /files/etc/mysql/conf.d/60-ssl.cnf/target[.='mysqld']/ssl-key  /etc/pki/tls/private/${FN}.key
	set /files/etc/mysql/conf.d/60-ssl.cnf/target[.='mysqld']/ssl-cert /etc/pki/tls/certs/${FN}.crt
	set /files/etc/mysql/conf.d/60-ssl.cnf/target[.='mysqld']/require_secure_transport ON

	set /files/etc/mysql/conf.d/70-replication.cnf/target[.='mysqld'] 'mysqld'
	set /files/etc/mysql/conf.d/70-replication.cnf/target[.='mysqld']/bind-address '*'
	set /files/etc/mysql/conf.d/70-replication.cnf/target[.='mysqld']/server-id ${SERVER_ID}
	set /files/etc/mysql/conf.d/70-replication.cnf/target[.='mysqld']/report-host ${SELF}
	set /files/etc/mysql/conf.d/70-replication.cnf/target[.='mysqld']/auto_increment_offset ${SERVER_ID}

	set /files/root/.my.cnf/target[.='mysql'] 'mysql'
	set /files/root/.my.cnf/target[.='mysql']/ssl-cipher ECDHE-ECDSA-AES256-GCM-SHA384
	set /files/root/.my.cnf/target[.='mysqldump'] 'mysqldump'
	set /files/root/.my.cnf/target[.='mysqldump']/ssl-cipher ECDHE-ECDSA-AES256-GCM-SHA384
	set /files/root/.my.cnf/target[.='mysqladmin'] 'mysqladmin'
	set /files/root/.my.cnf/target[.='mysqladmin']/ssl-cipher ECDHE-ECDSA-AES256-GCM-SHA384
	EOD


#
# We need to restart mysqld here becuase the client will immediately pick up the ssl settings, but
# the server needs a restart first, otherwise all our grants etc will fail
#
item " * Restarting mysqld to apply configuration changes"
systemctl restart mysqld


section "Updating mysql GRANTs"
# TODO: Can we just load this hash MySQL direct? it *MUST* match the MySQL root
#       password generated in init-mysql-server
item " * Granting root privileges for replication peers"
ROOTPASS=$(ng-mk-auth-token -f /etc/sysconfig/authseed mysql:root@localhost 16)
for i in $SELF $PEER; do
        echo "   CREATE USER IF NOT EXISTS 'root'@'$i' IDENTIFIED BY '$ROOTPASS' REQUIRE SSL;"
        mysql -NB -e "CREATE USER IF NOT EXISTS 'root'@'$i' IDENTIFIED BY '$ROOTPASS' REQUIRE SSL;"

        echo "   GRANT ALL PRIVILEGES ON *.* TO 'root'@'$i';"
        mysql -NB -e "GRANT ALL PRIVILEGES ON *.* TO 'root'@'$i';"
done

item " * Granting replication slave privileges for replication peers"
REPLPASS=$(ng-mk-auth-token -f /etc/sysconfig/authseed mysql:replicate@localhost 16)
for i in $SELF $PEER; do
        echo "   CREATE USER IF NOT EXISTS 'replicate'@'$i' IDENTIFIED BY '$REPLPASS' REQUIRE x509;"
        mysql -NB -e "CREATE USER IF NOT EXISTS 'replicate'@'$i' IDENTIFIED BY '$REPLPASS' REQUIRE x509;"

        echo "   GRANT REPLICATION SLAVE ON *.* TO 'replicate'@'$i';"
        mysql -NB -e "GRANT REPLICATION SLAVE ON *.* TO 'replicate'@'$i';"
done

section "Configuring replication source (CHANGE REPLICATION SOURCE)"
# stop the existing replica as it running will block the change replication command
mysql -NB -e 'STOP REPLICA';

echo -e "   CHANGE REPLICATION SOURCE TO SOURCE_HOST = '${PEER}',\n" \
   "                 SOURCE_USER       = 'replicate',\n" \
   "                 SOURCE_PORT       = 3306,\n" \
   "                 SOURCE_SSL        = 1,\n" \
   "                 SOURCE_PASSWORD   = '${REPLPASS}',\n" \
   "                 SOURCE_SSL_CA     = '/etc/pki/tls/certs/mysql.ca-bundle',\n" \
   "                 SOURCE_SSL_CERT   = '/etc/pki/tls/certs/${FN}.crt',\n" \
   "                 SOURCE_SSL_CIPHER = 'ECDHE-ECDSA-AES256-GCM-SHA384',\n" \
   "                 SOURCE_SSL_KEY    = '/etc/pki/tls/private/${FN}.key';\n" \
| mysql -v -NB | sed -re 's/^/   /g'

# Now actually start the replication
mysql -NB -e 'START REPLICA';

