#!/bin/bash

set -e

help() {
    echo "Usage: $(basename $0) create [options]" >&2
    echo "       $(basename $0) remove [options]" >&2
    echo "       $(basename $0) check  [options]" >&2
    echo ""
    echo "Options for the 'create' command:"
    echo "    --db <database-name>         Name of the database to create"
    echo "    --user <username>            Database user to create"
    echo "    --password <password>        Password for the database user"
    echo "    --autogenpw                  Auto generate a password for the database user"
    echo "    --standalone                 Configure the database server independent of Uyuni or SUSE Manager Server Database"
    echo "    --address <local-addresses>  Comma-separated list of local addresses to listen on"
    echo "                                 Use '*' for all addresses"
    echo "    --remote <remote-addresses>  Comma-separated list of remote addresses to allow connections from"
    echo "                                 Use address/netmask format"
    echo ""
    echo "Options for the 'remove' command:"
    echo "    --db <database-name>         Name of the database to remove"
    echo "    --user <username>            Name of the user to remove"
    echo ""
    echo "Options for the 'check' command:"
    echo "    --db <database-name>         Name of the database to check for"
    echo "    --user <username>            Name of the user to check for"
}

ask() {
    if $1; then
        read -e -p "$2" $3
    else
        read -e -s -p "$2" $3 && echo
    fi
}

isSUSE() {
    if [ ! -e '/etc/os-release' ]; then
        return 1
    fi
    if `grep -iq '^ID_LIKE=.*suse' /etc/os-release`; then
        return 0
    fi
    return 1
}

ask_check() {
    while true; do
        ask "$1" "$2" $3 || echo
        [[ "${!3}" =~ $4 ]] && break
    done
}

def_regexes() {
    local digit seqence n
    local IPv4_addr IPv4_mask IPv6_addr IPv6_mask
    digit='([0-9]|[1-9][0-9]|1[0-9][0-9]|2([0-4][0-9]|5[0-5]))'
    IPv4_addr="($digit\\.){3}$digit"
    IPv4_mask='([0-9]|[12][0-9]|3[012])'

    seqence='[[:digit:]abcdefABCDEF]{1,4}'
    IPv6_addr="$seqence(:$seqence){7}|::"

    # shortened
    IPv6_addr+="|:(:$seqence){1,7}"
    for n in 1 2 3 4 5 6; do
        IPv6_addr+="|($seqence:){$n}(:|(:$seqence){1,$((7-n))})"
    done
    IPv6_addr+="|($seqence:){7}:"

    # with IPv4 mixed

    IPv6_addr+="|($seqence:){6}$IPv4_addr|::$IPv4_addr"
    for n in 1 2 3 4 5; do
        IPv6_addr+="|($seqence:){$n}(:$seqence){0,$((5-n))}:$IPv4_addr"
    done

    # final wrap
    IPv6_addr="($IPv6_addr)"
    IPv6_mask="([0-9]|[1-9][0-9]|1[01][0-9]|12[0-8])"

    local addr="[[:space:]]*($IPv4_addr|$IPv6_addr)[[:space:]]*"
    Local_RE="([[:space:]]*|($addr,)*$addr)"
    local masked="[[:space:]]*($IPv4_addr/$IPv4_mask|$IPv6_addr/$IPv6_mask)[[:space:]]*"
    Remote_RE="(($masked,)*$masked)"
}
def_regexes
unset -f def_regexes

PG_DATA=/var/lib/pgsql/data
PG_HBA="$PG_DATA/pg_hba.conf"
PG_IDENT="$PG_DATA/pg_ident.conf"
POSTGRESQL="$PG_DATA/postgresql.conf"
PORT=5432
PG_PIDFILE="/run/postmaster.$PORT.pid"
if isSUSE ; then
    PG_PIDFILE="$PG_DATA/postmaster.pid"
fi
PG_SOCKET="/tmp/.s.PGSQL.$PORT"
SPACEWALK_TARGET="/usr/lib/systemd/system/spacewalk.target"
SERVICE_LIST="/etc/rhn/service-list"
RHN_CONF="/etc/rhn/rhn.conf"

LSOF="/usr/sbin/lsof"
if [ -x /usr/bin/lsof ]; then
    LSOF="/usr/bin/lsof"
fi
RUNUSER=runuser
FQDN=$(hostname -f)
SSL_CERT=/etc/pki/tls/certs/spacewalk.crt
SSL_KEY=/etc/pki/tls/private/pg-spacewalk.key
CA_CERT=/etc/pki/trust/anchors/LOCAL-RHN-ORG-TRUSTED-SSL-CERT
if [ ! -d /etc/pki/trust/anchors ]; then
    CA_CERT=/etc/pki/ca-trust/source/anchors/LOCAL-RHN-ORG-TRUSTED-SSL-CERT
fi


create() {
    if $LOCAL ; then
        ADDRESS="127.0.0.1"
        REMOTE="127.0.0.1/32,::1/128"
        FQDN="localhost"
    else
        [ ! -s "$SSL_CERT" ] && {
            echo "SSL Certificate ($SSL_CERT) is required to setup the reporting database" >&2
            exit 1
        }
        [ ! -s "$CA_CERT" ] && {
            echo "The SSL CA Certificate ($CA_CERT) is required to setup the reporting database" >&2
            exit 1
        }
        [ ! -s "$SSL_KEY" ] && {
            if [ -s /etc/pki/tls/private/spacewalk.key ]; then
                # SUMA 4.2 and earlier did not create the postgresql private key file
                cp /etc/pki/tls/private/spacewalk.key $SSL_KEY
            chown postgres:postgres $SSL_KEY
            chmod 0600 $SSL_KEY
            else
                echo "SSL Private Key ($SSL_KEY) not found" >&2
                exit 1
            fi
        }
    fi

    [ -z "$PGNAME" ] && ask true "Database name: " PGNAME
    [ -z "$PGUSER" ] && ask true "Database user: " PGUSER
    [ -z "$PGPASSWORD" ] && ask false "Database password: " PGPASSWORD

    [ -z "$ADDRESS" ] && ask_check true "Local addresses to listen on (comma-separated, RETURN for all): " ADDRESS "^$Local_RE\$"
    [ -z "$ADDRESS" ] && ADDRESS="*"
    [ -z "$REMOTE" ] && ask_check true "Remote addresses to allow connection from (address/netmask format, comma-separated): " REMOTE "^$Remote_RE\$"

    if exists_db ; then
        echo "Database '$PGNAME' already exists"
	exit 1
    fi
    if exists_user ; then
        echo "User '$PGUSER' already exists. Re-using configured password"
        exit 1
    fi

    postgresql_service enable

    if [ ! -d "$PG_DATA/base" ]; then
        PGHOME=$(getent passwd postgres | awk -F: '{print $6}')
        if test -x /usr/sbin/selinuxenabled && /usr/sbin/selinuxenabled; then
            SEMODE=$(/usr/sbin/getenforce)
            if [ "$SEMODE" = 'Permissive' -o "$SEMODE" = 'Enforcing' ]; then
                RCONOUT=$(/sbin/restorecon -Rnv $PGHOME)
                if [ -n "$RCONOUT" ]; then
                    echo
                    echo "The directory \"$PGHOME\" does not seem to have correct SELinux context."
                    echo
                    echo "Please fix the SELinux context by running:"
                    echo
                    echo "    restorecon -Rv $PGHOME"
                    echo
                    exit 1
                fi
            fi
        fi
        echo -e 'LC_CTYPE=en_US.UTF-8\nexport LC_CTYPE' >$PGHOME/.i18n
        postgresql_service initdb
    fi

    if $STANDALONE; then
        sed -i 's/^\(\s*listen_addresses.*\)$/### next line has been commented out by uyuni-setup-reportdb ###\n##\1/' $POSTGRESQL
        sed -i 's/^\(\s*max_connections.*\|\s*shared_buffers.*\)$/### next line has been commented out by uyuni-setup-reportdb ###\n##\1/' $POSTGRESQL

        cat >> $POSTGRESQL <<EOF
### uyuni-setup-reportdb modified values
checkpoint_completion_target = 0.7
effective_cache_size = 1152MB
log_line_prefix = '%m '
maintenance_work_mem = 96MB
max_connections = 100
shared_buffers = 384MB
wal_buffers = 4MB
work_mem = 2560kB
EOF
    fi

    if ! $LOCAL ; then
        # SSL setup is required when opening DB to the world
        postgres_reconfig "ssl" "on"
        postgres_reconfig "ssl_cert_file" "'$SSL_CERT'"
        postgres_reconfig "ssl_key_file" "'$SSL_KEY'"
    fi

    echo "$ADDRESS"|grep -q '127.0.0.1\|*' || ADDRESS="$ADDRESS, 127.0.0.1"
    postgres_reconfig "listen_addresses" "'$ADDRESS'"

    sysctl kernel.shmmax | ( read v v v ; LIMIT=5000000000 ; if (("$v"0 < $LIMIT )) ; then sysctl "kernel.shmmax=$LIMIT" >> /etc/sysctl.conf ; fi )

    if $STANDALONE; then
        sed -i 's/^\([^#].*\)$/### next line has been commented out by uyuni-setup-reportdb: ###\n##\1/ ' $PG_HBA
	pg_hba_set "local" "all" "all" "" "peer"
    fi
    pg_hba_set "local" "$PGNAME" "postgres" "peer"
    pg_hba_set "local" "$PGNAME" "all" "scram-sha-256"
    pg_hba_set "host"  "$PGNAME" "all" "127.0.0.1/32" "scram-sha-256"
    pg_hba_set "host"  "$PGNAME" "all" "::1/128" "scram-sha-256"

    for ADDR in $(echo $REMOTE|sed 's/,/ /g'); do
        pg_hba_set "host" "$PGNAME" "all" "$ADDR" "scram-sha-256"
    done

    /usr/bin/uyuni-sort-pg_hba

    # Create & configure /etc/rhn/rhn.conf
    RHN_CONF_DIR=$(dirname $RHN_CONF)
    if [ -d $RHN_CONF_DIR ]; then
        chmod 0710 $RHN_CONF_DIR
    else
        mkdir -m 0710 $RHN_CONF_DIR
    fi

    if $STANDALONE; then
        cat > $RHN_CONF <<EOF
report_db_backend = postgresql
report_db_user = $PGUSER
report_db_password = $PGPASSWORD
report_db_name = $PGNAME
report_db_host = $FQDN
report_db_port = $PORT
report_db_ssl_enabled = 1
report_db_sslrootcert = $CA_CERT
EOF
    else
        rhn_reconfig "report_db_backend" "postgresql"
        rhn_reconfig "report_db_user" "$PGUSER"
        rhn_reconfig "report_db_password" "$PGPASSWORD"
        rhn_reconfig "report_db_name" "$PGNAME"
        rhn_reconfig "report_db_host" "$FQDN"
        rhn_reconfig "report_db_port" "$PORT"
	if ! $LOCAL ; then
            rhn_reconfig "report_db_ssl_enabled" "1"
            rhn_reconfig "report_db_sslrootcert" "$CA_CERT"
        fi
    fi

    chmod 640 $RHN_CONF

    postgresql_service status >& /dev/null && postgresql_service stop
    postgresql_service start

    if $LSOF /proc > /dev/null ; then
        while [ -f "$PG_PIDFILE" ] ; do
            # wait for postmaster to be ready
            $LSOF -t -p $(head -1 "$PG_PIDFILE" 2>/dev/null) -a "$PG_SOCKET" > /dev/null  \
                && break
            sleep 1
        done
    fi

    if ! exists_db ; then
            $RUNUSER - postgres -c "createdb -E UTF8 '$PGNAME'"
    fi
    if ! exists_plpgsql ; then
            EXTENSION=$($RUNUSER - postgres -c 'psql -c "CREATE EXTENSION IF NOT EXISTS plpgsql;" -d '$PGNAME'')
    fi
    if ! exists_user ; then
            $RUNUSER - postgres -c "yes '$PGPASSWORD' | createuser -P -sDR '$PGUSER'" 2>/dev/null
    fi

    postgresql_service reload

    if ! exists_schema ; then
            /usr/bin/spacewalk-sql --reportdb /etc/sysconfig/rhn/reportdb/main.sql
    else
	    /usr/bin/spacewalk-schema-upgrade --reportdb -y
    fi

}

remove() {
    if [ -z "$PGUSER" -a -z "$PGNAME" ] ; then
        help
        exit 1
    fi
    if exists_db ; then
            $RUNUSER - postgres -c "dropdb '$PGNAME'"
    fi
    if exists_user ; then
            $RUNUSER - postgres -c "dropuser '$PGUSER'"
    fi
    rhn_reconfig "report_db_backend" ""
    rhn_reconfig "report_db_user" ""
    rhn_reconfig "report_db_password" ""
    rhn_reconfig "report_db_name" ""

    pg_hba_remove "host" "$PGNAME" "all"
    pg_hba_remove "local" "$PGNAME" "all"

}

check() {
    if [ -z "$PGUSER" -a -z "$PGNAME" ] ; then
        help
        exit 1
    fi

    postgresql_service status >& /dev/null || postgresql_service start

    RET=0
    if [ -n "$PGUSER" ] ; then
        if exists_user ; then
            echo "User \"$PGUSER\" already exists"
        else
            echo "User \"$PGUSER\" does not exist"
            RET=1
        fi
    fi
    if [ -n "$PGNAME" ] ; then
        if exists_db ; then
            echo "Database \"$PGNAME\" already exists"
        else
            echo "Database \"$PGNAME\" does not exist"
            RET=1
        fi
    fi
    exit $RET
}

is_postgres10() {
    NUM=$($RUNUSER - postgres -c 'psql -t -c "SHOW server_version_num;"')
    if (( $NUM > 100000 )) ; then
        return 0
    else
        return 1
    fi
}

exists_db() {
    EXISTS=$($RUNUSER - postgres -c 'psql -t -c "select datname from pg_database where datname='"'$PGNAME'"';"')
    if [ "x$EXISTS" == "x $PGNAME" ] ; then
        return 0
    else
        return 1
    fi
}

exists_plpgsql() {
    EXISTS=$($RUNUSER - postgres -c 'psql -At -c "select lanname from pg_catalog.pg_language where lanname='"'plpgsql'"';"'" $PGNAME")
    if [ "x$EXISTS" == "xplpgsql" ] ; then
        return 0
    else
        return 1
    fi
}

exists_user() {
    EXISTS=$($RUNUSER - postgres -c 'psql -t -c "select usename from pg_user where usename='"'$PGUSER'"';"')
    if [ "x$EXISTS" == "x $PGUSER" ] ; then
        return 0
    else
        return 1
    fi
}

exists_schema() {
    EXISTS=$(spacewalk-sql --reportdb --select-mode - <<< "select 1 from VersionInfo;" 2>/dev/null)
    return $?
}

rhn_reconfig() {
    if grep -E "^$1[[:space:]]*=" $RHN_CONF >/dev/null; then
	sed -i "s|^$1[[:space:]]*=.*|$1 = $2|" $RHN_CONF
    else
        echo "$1 = $2" >> $RHN_CONF
    fi
}

postgres_reconfig() {
    if grep -E "^$1[[:space:]]*=" $POSTGRESQL >/dev/null; then
	sed -i "s|^$1[[:space:]]*=.*|$1 = $2|" $POSTGRESQL
    else
        echo "$1 = $2" >> $POSTGRESQL
    fi
}

pg_hba_set() {
    if ! grep -E "^$1[[:space:]]+$2[[:space:]]+$3[[:space:]]+$4[[:space:]]*$5" $PG_HBA >/dev/null; then
        echo -e "$1\t$2\t$3\t$4\t$5" >> $PG_HBA
    fi
}

pg_hba_remove() {
    sed -i "s|^$1[[:space:]]\+$2[[:space:]]\+$3[[:space:]].*||g" $PG_HBA
}

postgresql_service() {
    case $1 in
        initdb)
            if isSUSE ; then
                # the start script initialize the DB
		if $LOCAL ; then
                    $RUNUSER - postgres -c "/usr/share/postgresql/postgresql-script start"
                else
                    systemctl start postgresql
                fi
	    else
                postgresql-setup initdb
	    fi
	    ;;
        status)
            if $LOCAL ; then
                pgrep -x postgresql >/dev/null && return 1 || return 0
            else
                systemctl $1 postgresql
            fi
            ;;
        enable)
            if ! $LOCAL ; then
                systemctl $1 postgresql
            fi
            ;;
        *)
            if $LOCAL ; then
                echo ">> $1"
                $RUNUSER - postgres -c "/usr/share/postgresql/postgresql-script $1"
            else
                systemctl $1 postgresql
            fi
            ;;
    esac
}

OPTS=$(getopt --longoptions=db:,user:,password:,autogenpw,standalone,local,help,address:,remote: -n ${0##*/} -- d:u:p:gsha:r:l "$@")

if [ $? != 0 ] ; then echo "Terminating..." >&2 ; exit 1 ; fi

eval set -- "$OPTS"

PGNAME=""
PGUSER=""
PGPASSWORD=""
STANDALONE=false
ADDRESS=""
REMOTE=""
LOCAL=false

while true ; do
    case "$1" in
        -d|--db)
            PGNAME=$2
            shift
            ;;
        -u|--user)
            PGUSER=$2
            shift
            ;;
        -p|--password)
            if [ -n "$PGPASSWORD" ]; then
                echo "Invalid options: do not use --password together with --autgenpw!" >&2
                exit 1
            fi
            PGPASSWORD=$2
            shift
            ;;
	-g|--autogenpw)
            if [ -n "$PGPASSWORD" ]; then
                echo "Invalid options: do not use --password together with --autgenpw!" >&2
                exit 1
            fi
            PGPASSWORD=$(head -10 /dev/urandom | tr -dc '[:alnum:]' | fold -w 25 | head -n 1)
            ;;
        -s|--standalone)
            STANDALONE=true
            ;;
        -a|--address)
            ADDRESS=$2
            shift
            ;;
        -r|--remote)
            REMOTE=$2
            shift
            ;;
        -h|--help)
            help;
            exit 0;
            ;;
	-l|--local)
            LOCAL=true
            ;;
        --)
            shift
            break
            ;;
        *)
            echo "Internal error [$1]!" >&2
            exit 1
            ;;
    esac
    shift
done

case $1 in
        create) create
            ;;
        remove) remove
            ;;
        check)  check
            ;;
        *)      help
            ;;
esac
