#!/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 "    --standalone                 Configure the database server to be remotely accessible"
    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
    source /etc/os-release
    if echo $CPE_NAME | grep -E 'cpe:/o:(open)*suse:' >/dev/null ; 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"
POSTGRESQL_SW_APPEND=/usr/share/spacewalk/setup/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
if isSUSE ; then
    RUNUSER=/usr/bin/su
fi

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

    if $STANDALONE; then
        [ -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\$"
    fi

    postgresql_service enable

    if [ ! -d "$PG_DATA/base" ] ; then
        PGHOME=$(getent passwd postgres | awk -F: '{print $6}')
        if /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 spacewalk-setup-postgresql ###\n##\1/' $POSTGRESQL
    fi

    # see bug 821446, we enable timestamps in logs by default
    sed -i 's/^\(\s*max_connections.*\|\s*shared_buffers.*\)$/### next line has been commented out by spacewalk-setup-postgresql ###\n##\1/' $POSTGRESQL
    cat $POSTGRESQL_SW_APPEND >> $POSTGRESQL

    if $STANDALONE; then
        echo "$ADDRESS"|grep -q '127.0.0.1\|*' || ADDRESS="$ADDRESS, 127.0.0.1"
        cat >> $POSTGRESQL <<EOF
### spacewalk-setup-postgresql modified values for a standalone PostgreSQL database
listen_addresses='$ADDRESS'
EOF
    fi

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

    cat >> $PG_IDENT <<EOF

usermap root postgres
usermap postgres postgres

EOF

    sed -i 's/^\([^#].*\)$/### next line has been commented out by spacewalk-setup-postgresql: ###\n##\1/ ' $PG_HBA
    cat >> $PG_HBA <<EOF

local $PGNAME postgres peer
local $PGNAME $PGUSER scram-sha-256
host  $PGNAME $PGUSER 127.0.0.1/8 scram-sha-256
host  $PGNAME $PGUSER ::1/128 scram-sha-256
local $PGNAME postgres ident map=usermap
local postgres postgres ident map=usermap

EOF

    if $STANDALONE; then
        for ADDR in $(echo $REMOTE|sed 's/,/ /g'); do
            cat >> $PG_HBA <<EOF
host $PGNAME $PGUSER $ADDR scram-sha-256
EOF
        done

        # 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

        cat > $RHN_CONF <<EOF
db_backend = postgresql
db_user = $PGUSER
db_password = $PGPASSWORD
db_name = $PGNAME
db_host = localhost
db_port =
EOF
        chmod 640 $RHN_CONF

    fi

    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 $(cat "$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
            if is_postgres10 ; then
                EXTENSION=$($RUNUSER - postgres -c 'psql -c "CREATE EXTENSION IF NOT EXISTS plpgsql;" -d '$PGNAME'')
            else
                $RUNUSER - postgres -c "createlang plpgsql '$PGNAME'"
            fi
    fi
    if ! exists_pltclu ; then
            if is_postgres10 ; then
                EXTENSION=$($RUNUSER - postgres -c 'psql -c "CREATE EXTENSION IF NOT EXISTS pltclu;" -d '$PGNAME'')
            else
                $RUNUSER - postgres -c "createlang pltclu '$PGNAME'"
            fi
    fi
    if ! exists_user ; then
            $RUNUSER - postgres -c "yes '$PGPASSWORD' | createuser -P -sDR '$PGUSER'" 2>/dev/null
    fi

    postgresql_service reload

    if [ -e "$SPACEWALK_TARGET" ] ; then
        if ! grep -q 'Requires=postgresql.service' "$SPACEWALK_TARGET" ; then
             sed -i 's/\(Description=Spacewalk\)/\1\nRequires=postgresql.service/' "$SPACEWALK_TARGET"
        fi
    elif [ -e "$SERVICE_LIST" ]; then
        if ! grep -q 'SERVICES=.*postgresql' "$SERVICE_LIST" ; then
             echo '' >>"$SERVICE_LIST"
             echo '# added by spacewalk-setup-postgresql' >>"$SERVICE_LIST"
             echo 'SERVICES="postgresql $SERVICES"' >>"$SERVICE_LIST"
        fi
    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
}

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_pltclu() {
    EXISTS=$($RUNUSER - postgres -c 'psql -At -c "select lanname from pg_catalog.pg_language where lanname='"'pltclu'"';"'" $PGNAME")
    if [ "x$EXISTS" == "xpltclu" ] ; 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
}

postgresql_service() {
    if [ -e "$SPACEWALK_TARGET" ] ; then
        case $1 in
            initdb) postgresql-setup initdb ;;
                 *) systemctl $1 postgresql ;;
        esac
    else
        case $1 in
            enable) chkconfig postgresql on ;;
                 *) service postgresql $1 ;;
        esac
    fi
}

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

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

eval set -- "$OPTS"

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

while true ; do
    case "$1" in
        -d|--db)
            PGNAME=$2
            shift
            ;;
        -u|--user)
            PGUSER=$2
            shift
            ;;
        -p|--password)
            PGPASSWORD=$2
            shift
            ;;
        -s|--standalone)
            STANDALONE=true
            ;;
        -a|--address)
            ADDRESS=$2
            shift
            ;;
        -r|--remote)
            REMOTE=$2
            shift
            ;;
        -h|--help)
            help;
            exit 0;
            ;;
        --)
            shift
            break
            ;;
        *)
            echo "Internal error [$1]!" >&2
            exit 1
            ;;
    esac
    shift
done

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