#!/bin/bash

# Copyright (c) Veeam Software Group GmbH

set -eE -u -o pipefail

SCRIPT_DIR=$(dirname "$(realpath "${BASH_SOURCE[0]}")"); readonly SCRIPT_DIR;
source "$SCRIPT_DIR/lib/error-handle.bash"
source "$SCRIPT_DIR/lib/optparser.bash"
source "$SCRIPT_DIR/lib/verbose.bash"
source "$SCRIPT_DIR/lib/strarray.bash"

setlocale en_US.utf8 || :

# shellcheck disable=SC2034
declare -A op=()
declare -A ARGS=()

optparser-init op \
    --desc "Initialize and configure the PostgreSQL database."
optparser-addopt op -s "-d" -l "--pgdata" --narg 1 \
    --help "override data path (\$PGDATA)"
optparser-addopt op -s "-l" -l "--logdir" --narg 1 \
    --help "override log directory"
optparser-addopt op -l "--logdir-group" --narg 1 \
    --default "postgres" \
    --help "set group ownership for the log directory"
optparser-addopt op -l "--logdir-mode" --narg 1 \
    --default "00700" \
    --help "set the permission mode for the log directory"
optparser-addopt op -l "--logfile-mode" --narg 1 \
    --help "set the permission mode for the log files"
optparser-addopt op -s "-a" -l "--alias" --narg '*' \
    --help "add service name alias"
optparser-addopt op -l "--wipe" \
    --help "wipe old data if it exists"
optparser-addopt op -s "-v" -l "--verbose" \
    --help "provide more detailed output"
optparser-addopt op -s "-h" -l "--help" \
    --help "show this help message and exit"
optparser-ignore-required op help
optparser-parse op ARGS "$@" || exit 1
[[ -n ${ARGS[help]:-} ]] && { optparser-help op; exit 0; }
[[ -n ${ARGS[verbose]:-} ]] && { verbose-enable; }
[[ ${ARGS[pgdata]:-} = *' '* ]] \
    && die "PostgreSQL initdb script does not support spaces in the pgdata directory path"

[[ $EUID -eq 0 ]] || die "script should be run as root"

trap-init

eval "$("$SCRIPT_DIR/pgsql-pkginfo")"

[[ -n ${VCTL_PGSQL_PKGNAME:-} && -n ${VCTL_PGSQL_VERSION:-} ]] \
    || die "failed to find installed PostgreSQL"
[[ -n ${VCTL_PGSQL_SETUP:-}   ]] || die "failed to find setup script"
[[ -n ${VCTL_PGSQL_SVCFILE:-} ]] || die "failed to find systemd unit file"
[[ -n ${VCTL_PGSQL_PSQL:-}    ]] || die "failed to find psql binary"

verbose-printf "%-20s: %s\n" "package name" "$VCTL_PGSQL_PKGNAME"
verbose-printf "%-20s: %s\n" "version" "$VCTL_PGSQL_VERSION"
verbose-printf "%-20s: %s\n" "setup script" "$VCTL_PGSQL_SETUP"
verbose-printf "%-20s: %s\n" "systemd unit file" "$VCTL_PGSQL_SVCFILE"
verbose-printf "%-20s: %s\n" "psql binary" "$VCTL_PGSQL_PSQL"

# This script can be run during system startup. So we need to prevent the
# PostgreSQL service from starting by disabling and forcefully stopping it.

if systemctl is-enabled --quiet "$VCTL_PGSQL_SVCNAME"; then
    echo "Disabling $VCTL_PGSQL_SVCNAME"
    systemctl disable "$VCTL_PGSQL_SVCNAME" || true
fi
if systemctl is-active --quiet "$VCTL_PGSQL_SVCNAME"; then
    echo "Stopping $VCTL_PGSQL_SVCNAME"
    systemctl stop "$VCTL_PGSQL_SVCNAME" || die "failed to stop $VCTL_PGSQL_SVCNAME"
else
    systemctl stop "$VCTL_PGSQL_SVCNAME" || true
fi

add-svc-dropin() {
    local conf=$1
    local dropin="/etc/systemd/system/${VCTL_PGSQL_SVCNAME}.d"
    # shellcheck disable=SC2046
    mkdir -p $(verbose-arg -v) "$dropin"
    cat > "$dropin/$conf"
    if is-verbose; then
        echo "$dropin/$conf"
        cat "$dropin/$conf"
    fi
    systemctl daemon-reload
}

: "${VCTL_PGSQL_PGDATA:=}"
if [[ ${ARGS[pgdata]:-$VCTL_PGSQL_PGDATA} != "$VCTL_PGSQL_PGDATA" ]]; then
    echo "Changing PGDATA to '${ARGS[pgdata]}'"
    add-svc-dropin "10-pgdata.conf" \
        <<< $'[Service]\nEnvironment=PGDATA='"${ARGS[pgdata]}"
    eval "$("$SCRIPT_DIR/pgsql-pkginfo")"
    [[ ${ARGS[pgdata]} == "$VCTL_PGSQL_PGDATA" ]] \
        || die "requested and actual \$PGDATA are different"
fi
[[ -n ${VCTL_PGSQL_PGDATA:-} ]] \
    || die "\$PGDATA not found in $VCTL_PGSQL_SVCNAME configuration"
verbose-printf "%-20s: %s\n" "\$PGDATA" "$VCTL_PGSQL_PGDATA"

if [[ -n ${ARGS[alias]:-} ]]; then
    verbose-printf "%-20s: %s\n" "svc alias:" "$(strarray-join " " "${ARGS[alias]:-}")"
    add-svc-dropin "11-alias.conf" \
        <<< $'[Install]\n'"Alias=$(strarray-join " " "${ARGS[alias]:-}")"
fi

readonly SOCKET_DIR="/run/postgresql"
readonly HBA_CONF=$'
# TYPE  DATABASE        USER            ADDRESS                 METHOD
local   all             postgres                                peer
local   all             all                                     scram-sha-256
local   replication     postgres                                peer
local   replication     all                                     scram-sha-256
'

# shellcheck disable=SC2046
[[ -n ${ARGS[wipe]:-} ]] && rm -rf $(verbose-arg -v) "$VCTL_PGSQL_PGDATA"

# shellcheck disable=SC2046
install $(verbose-arg -v) -o postgres -g postgres -m 00700 -D -d "$VCTL_PGSQL_PGDATA"

# shellcheck disable=SC2046
[[ -n ${ARGS[logdir]:-} ]] \
    && install $(verbose-arg -v) -o postgres -g "${ARGS[logdir-group]}" -m "${ARGS[logdir-mode]}" -D -d "${ARGS[logdir]}"

if [[ ! -f "$VCTL_PGSQL_PGDATA/PG_VERSION" ]]; then
    echo "Initializing new PGDATA at $VCTL_PGSQL_PGDATA"

    setup_args=("initdb")
    grep -q -- '--initdb' "$VCTL_PGSQL_SETUP" && setup_args=("--initdb")

    initdb_args=(
        --data-checksums
        -c listen_addresses=''
        -c unix_socket_directories="$SOCKET_DIR"
    )
    [[ -n ${ARGS[logdir]:-} ]] \
        && initdb_args+=(-c log_directory="${ARGS[logdir]}")

    [[ -n ${ARGS[logfile-mode]:-} ]] \
        && initdb_args+=(-c log_file_mode="${ARGS[logfile-mode]}")

    if grep -q -- '-l postgres\b.*initdb' "$VCTL_PGSQL_SETUP"; then
        postgres_shell=$(getent passwd postgres | awk -F : '{print $NF}')
        case "$postgres_shell"
            in /sbin/nologin|/bin/false)
                usermod_required=1
            ;;
        esac
    fi
    (
        verbose-printf "setup args      : %s\n" "$(shescape "${setup_args[@]}")"
        verbose-printf "initdb args     : %s\n" "$(shescape "${initdb_args[@]}")"
        verbose-printf "usermod required: %s\n" "${usermod_required:-}"

        if [[ -n ${usermod_required:-} ]]; then
            at-exit usermod -s "$postgres_shell" postgres
            usermod -s "/bin/sh" postgres
        fi

        PGSETUP_INITDB_OPTIONS=$(shescape "${initdb_args[@]}")
        export PGSETUP_INITDB_OPTIONS
        "$VCTL_PGSQL_SETUP" "${setup_args[@]}"
        cat > "$VCTL_PGSQL_PGDATA/pg_hba.conf" <<< "$HBA_CONF"
    )

    unset setup_args
    unset initdb_args
    unset postgres_shell
    unset usermod_required
else
    echo "Reconfiguring PGDATA at $VCTL_PGSQL_PGDATA"

    conf="$VCTL_PGSQL_PGDATA/postgresql.conf"
    auto="$VCTL_PGSQL_PGDATA/postgresql.auto.conf"
    get_val() {
        sed -n -e 's/^\s*'"$(sedescape "$2")"'\s*=\s*//;t trim;b' \
               -e ':trim' -e 's/#.*$//;s/\s*$//;p' \
               "$1"
    }
    del_val() {
        verbose-cmd sed -i -e '/^\s*'"$(sedescape "$2")"'\s*=/d' "$1"
    }
    set_val() {
        verbose-cmd sed -i -e '/^\s*'"$(sedescape "$2")"'\s*=/d' \
                           -e "\$a\\" -e "$2 = $3" \
                           "$1"
    }

    if [[ -f "$auto" ]]; then
        del_val "$auto" "listen_addresses"
        del_val "$auto" "unix_socket_directories"
        del_val "$auto" "hba_file"
        [[ -n ${ARGS[logdir]:-} ]] && del_val "$auto" "log_directory"
        [[ -n ${ARGS[logfile-mode]:-} ]] && del_val "$auto" "log_file_mode"
    fi
    [[ $(get_val "$conf" listen_addresses) != "''" ]] \
        && set_val "$conf" listen_addresses "''"
    [[ $(get_val "$conf" unix_socket_directories) != "'$SOCKET_DIR'" ]] \
        && set_val "$conf" unix_socket_directories "'$SOCKET_DIR'"
    [[ -n ${ARGS[logdir]:-} && $(get_val "$conf" log_directory) != "'${ARGS[logdir]}'" ]] \
        && set_val "$conf" log_directory "'${ARGS[logdir]}'"
    [[ -n ${ARGS[logfile-mode]:-} && $(get_val "$conf" log_file_mode) != "${ARGS[logfile-mode]}" ]] \
        && set_val "$conf" log_file_mode "${ARGS[logfile-mode]}"

    unset conf
    unset auto
    unset get_val
    unset del_val
    unset set_val

    cat > "$VCTL_PGSQL_PGDATA/pg_hba.conf" <<< "$HBA_CONF"
fi

echo "Optimizing PostgreSQL configuration"
"$SCRIPT_DIR/vctl-setup-tool" postgresconfigurator "pgsqldatadirectory:$VCTL_PGSQL_PGDATA"
sed -i -e "/listen_addresses\s*=\s*'localhost'/s/localhost//" "$VCTL_PGSQL_PGDATA/postgresql.conf"

echo "Starting $VCTL_PGSQL_SVCNAME"
systemctl start "$VCTL_PGSQL_SVCNAME" || die "failed to start $VCTL_PGSQL_SVCNAME"

if ! systemctl is-enabled --quiet "$VCTL_PGSQL_SVCNAME"; then
    echo "Enabling $VCTL_PGSQL_SVCNAME"
    systemctl enable "$VCTL_PGSQL_SVCNAME" || die "failed to enable $VCTL_PGSQL_SVCNAME"
fi
