#!/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/sig.bash"
[[ $(sig-flags $$ PIPE) != --??* ]] \
    && sig-modify --exec --default PIPE -- "$BASH" "$0" "$@"

source "$SCRIPT_DIR/lib/common.bash"
source "$SCRIPT_DIR/lib/error-handle.bash"
source "$SCRIPT_DIR/lib/optparser-legacy.bash"
source "$SCRIPT_DIR/lib/str.bash"
source "$SCRIPT_DIR/lib/verbose.bash"
source "$SCRIPT_DIR/lib/logit.bash"

export VCTL_SETUP_TOOL_ROLE="vbr"

readonly LOGDIRDEFAULT="/var/log/VeeamBackup"
readonly LOGFILENAME="vbrctl.log"
readonly INSDIR="/opt/veeam/vbr"
readonly REGPATH="Veeam Backup and Replication"
readonly PGSQLCFG="$REGPATH/DatabaseConfigurations/PostgreSql"
readonly PGSQLVER="17.*"
readonly VBRSVCUSER="veeam-usr-vbr"
declare -A PLATFORM_SERVICE=(
    [ahv]=veeam-platform-service-ahv
    [aws]=veeam-platform-service-aws
    [kasten]=veeam-platform-service-kasten
    [pve]=veeam-platform-service-pve
    [azure]=veeam-platform-service-azure
    [scp]=veeam-platform-service-scp
)
readonly -A PLATFORM_SERVICE

main() {
    setlocale en_US.utf8 || :
    optparser-init "$0" "Veeam Backup and Replication control."

    optparser-addopt global \
        -s "-h" -l "--help" \
        --help "show this help message and exit"

    optparser-addopt global \
        -s "-v" -l "--verbose" \
        --help "provide more detailed output"

    optparser-addcmd init \
        --help "initialize system for VBR" \
        --help $'\n'"Components:"$'\n'"$({
            while read -r c; do optparser-fmthelp-line "${INIT[$c]}" "$c"; done \
                < <(printf "%s\n" "${!INIT[@]}" | sort)
        })" \
        --help $'\n'"Components default: ${INIT_DEFAULT[*]}" \
        --call cmd-init
    optparser-addopt init --name "COMPONENT" --narg "*"
    optparser-addopt init -l "--db-name" --narg 1 \
        --help "name for the configuration database" \
        --help "(db-local and db-remote)"
    optparser-addopt init -l "--db-server" --narg 1 \
        --help "SQL server and instance on which the configuration database will be deployed" \
        --help "(db-remote)"
    optparser-addopt init -l "--db-user" --narg 1 \
        --help "login to connect to the SQL server" \
        --help "(db-remote)"
    optparser-addopt init -l "--db-passwd" --narg 1 \
        --help "environment variable name in which a password is stored" \
        --help "(db-remote)"
    optparser-addopt init -l "--entra-id-db-server" --narg 1 \
        --help "Entra ID SQL server and instance on which the configuration database will be deployed" \
        --help "(db-local and db-remote)"
    optparser-addopt init -l "--entra-id-db-user" --narg 1 \
        --help "login to connect to the Entra ID SQL server." \
        --help "(db-local and db-remote)"
    optparser-addopt init -l "--entra-id-db-pass" --narg 1 \
        --help "environment variable name in which Entra ID password is stored" \
        --help "(db-local and db-remote)"
    optparser-addopt init -l "--components-autoupgrade" --narg 1
    optparser-addopt init -l "--license-autoupgrade" --narg 1
    optparser-addopt init -l "--deploy-mode" --narg 1
    optparser-addopt init -l "--db-wipe"

    optparser-addopt init -l "--no-db" \
        --help "do not install database" \
        --help "(deps)"
    optparser-addopt init -l "--with-platform-service" --narg 1 \
        --help "comma-separated string with platform service IDs that must be installed;" \
        --help "default: include all" \
        --help "id: $(printf "%s\n" "${!PLATFORM_SERVICE[@]}" | sort | xargs echo)" \
        --help "(deps)"
    optparser-addopt init -l "--without-platform-service" --narg 1 \
        --help "comma-separated string with platform service IDs that must be excluded from installation;" \
        --help "use the special value 'all' to exclude all available platform services"

    optparser-addcmd start \
        --help "start all VBR services" \
        --call cmd-start
    optparser-addopt start -s "-t" -l "--timeout" --narg 1 \
        --help "timeout for starting services;" \
        --help "floating point number with an optional suffix: s (seconds), m (minutes), h (hours), d (days)"
    optparser-addopt start -l "--exclude" --narg '1' # for back compability

    optparser-addcmd stop \
        --help "stop all VBR services" \
        --call cmd-stop
    optparser-addopt stop -s "-t" -l "--timeout" --narg 1 \
        --help "timeout for starting services;" \
        --help "floating point number with an optional suffix: s (seconds), m (minutes), h (hours), d (days)"
    optparser-addopt stop -l "--exclude" --narg '1' # for back compability

    optparser-addcmd restart \
        --help "restart all VBR services" \
        --call cmd-restart
    optparser-addopt restart -s "-t" -l "--timeout" --narg 1 \
        --help "timeout for starting services;" \
        --help "floating point number with an optional suffix: s (seconds), m (minutes), h (hours), d (days)"

    optparser-addcmd enable \
        --help "enable all VBR services" \
        --call cmd-enable

    optparser-addcmd disable \
        --help "disable all VBR services" \
        --call cmd-disable

    optparser-addcmd status \
        --help "show status for all VBR services" \
        --call cmd-status

    optparser-addcmd update --call cmd-update

    optparser-addcmd before-update --call cmd-before-update
    optparser-addopt before-update --name "VER-CUR" --help "current VBR version"
    optparser-addopt before-update --name "VER-NEW" --help "new VBR version"
    optparser-addopt before-update --name "ARGS" --narg "*"

    optparser-addcmd after-update --call cmd-after-update
    optparser-addopt after-update --name "VER-OLD" --help "old VBR version"
    optparser-addopt after-update --name "VER-CUR" --help "current VBR version"
    optparser-addopt after-update --name "ARGS" --narg "*"

    optparser-addcmd get-config \
        --help "get VBR configuration settings" \
        --call cmd-get-config
    optparser-addopt get-config --name "REGPATH" --help "registry path to option"

    optparser-addcmd set-config \
        --help "set VBR configuration settings" \
        --call cmd-set-config
    optparser-addopt set-config --name "REGPATH" --help "registry path to option"
    optparser-addopt set-config --name "VALUE" --narg "?" --help "value to set"

    optparser-addcmd del-config \
        --help "delete VBR configuration settings" \
        --call cmd-del-config
    optparser-addopt get-config --name "REGPATH" --help "registry path to option"

    optparser-addcmd gen-nginx-certs \
        --help "generate X.509 certificate for Nginx" \
        --call cmd-gen-nginx-certs
    optparser-addopt gen-nginx-certs -s "-f" -l "--force" \
        --help "overwrite existing certificates"

    optparser-addcmd install-postgresql \
        --help "install the PostgreSQL database and its additional components" \
        --call cmd-install-postgresql
    optparser-addopt install-postgresql -s "-f" -l "--force" \
        --help "force reconfigure and use the existing PostgreSQL with a compatible version"

    optparser-addcmd install-veeamdeployment \
        --help "install veeamdeployment package" \
        --call cmd-install-veeamdeployment

    optparser-addcmd install-veeamtransport \
        --help "install veeamtransport package" \
        --call cmd-install-veeamtransport

    optparser-addcmd install-platform-services \
        --help "install VBR platfrom services;" \
        --help "without arguments will install all available platform services;" \
        --help "to exclude platform service from installation, provide the argument as: 'no-ID'" \
        --help $'\navailable platfrom service IDs:\n'"$(printf "%s\n" "${!PLATFORM_SERVICE[@]}" | sort | column)" \
        --call cmd-install-platform-services
    optparser-addopt install-platform-services --name "ID" --narg "*"

    optparser-addcmd noop --call true

    optparser-parse "$@"

    local cmd=${ARGS[global/cmd]:-}
    if [[ -z $cmd || -n ${ARGS[global/help]:-} || -n ${ARGS[$cmd/help]:-} ]]; then
        optparser-help "$cmd"
        return 0
    fi

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

    local logdir
    logdir=$("$SCRIPT_DIR/vctl-config" get "$REGPATH/LogDirectory")
    [[ ${logdir:-} != "$LOGDIRDEFAULT" ]] || logdir=''
    install -m 02775 -o root -g veeam-grp-log -d "$LOGDIRDEFAULT" "$LOGDIRDEFAULT/Plugins" \
                                                 ${logdir:+"$logdir" "$logdir/Plugins"}
    loginit "${logdir:-$LOGDIRDEFAULT}/$LOGFILENAME" || :
    logit info "CMD  = $(shescape "$0" "$@")"
    logit info "PID  = $$"
    logit info "PPID = $(getppid) ($(cat "/proc/$(getppid)/comm" 2>/dev/null || :))"
    logit info "TTY  = $(tty 2>/dev/null || :)"
    # shellcheck disable=SC2016
    at-exit-eval 'logit info "$0 exited with code $VCTL_ERROR_HANDLE_FAIL_CODE"'

    declare -A os_release
    parse-os-release os_release
    case "${os_release[ID]}" in
        rocky | rhel) ;;
        *) die "unsupported OS \"${os_release[PRETTY_NAME]}\"" ;;
    esac
    if [[ -n ${ARGS[global/verbose]:-} ]]; then
        verbose-enable
    else
        verbose-disable
    fi
    export VCTL_VERBOSE
    "${_OPTPARSER[call/$cmd]}"
}

declare -A INIT=(
    [common]="common initialization"
    [db-local]="deploy local database"
    [db-remote]="deploy remote database"
    [db-update]="deploy database after update"
    [selinux]="configure SELinux"
    [nginx]="configure Nginx"
    [deps]="install additional dependencies"
    [iso]="initialize for ISO appliance"
)
INIT_DEFAULT=(deps common db-local selinux nginx)

cmd-init() {
    local -a comp; local c
    mapfile -t comp <<< "${ARGS[init/COMPONENT]:-}"
    unset "comp[-1]"
    (( ${#comp[@]} == 0 )) && comp=("${INIT_DEFAULT[@]}")
    for c in "${comp[@]}"; do
         declare -F "init-$c" >/dev/null || die "invalid component '$c'"
    done
    verbose-echo "Stopping services if needed"
    verbose-stdout "$0" stop
    for c in "${comp[@]}"; do
        "init-$c"
    done
}

init-deps() {
    echo "Installing dependencies"
    if [[ -z "${ARGS[init/no-db]:-}" ]]; then
        ARGS[install-postgresql/force]=1
        cmd-install-postgresql
    fi
    if [[ ${ARGS[init/without-platform-service]:-} != all ]]; then
        local -a with=(); local -a without=()
        str-split with "${ARGS[init/with-platform-service]-all}" ','
        str-split without "${ARGS[init/without-platform-service]:-}" ','
        local id
        for id in "${with[@]}"; do
            ARGS[install-platform-services/ID]+="$(str-trim "$id")"$'\n'
        done
        for id in "${without[@]}"; do
            ARGS[install-platform-services/ID]+='no-'"$(str-trim "$id")"$'\n'
        done
        cmd-install-platform-services
    fi
    cmd-install-veeamdeployment
    cmd-install-veeamtransport
}

init-common() {
    echo "Common initializing"
    return 0
}

init-db-local() {
    echo "Initializing local database"

    local dbname; local dbserver; local dbuser; local dbpasswd;
    [[ -n ${ARGS[init/db-name]:-} ]] \
        && dbname=${ARGS[init/db-name]} \
        || dbname=$("$SCRIPT_DIR/vctl-config" get "$PGSQLCFG/SqlDatabaseName")
    dbserver=$("$SCRIPT_DIR/pgsql-conf" show unix_socket_directories)
    dbserver=${dbserver%%,*}
    dbuser=veeam-usr-pgsql
    dbpasswd=$("$SCRIPT_DIR/genpass" 128)
    "$SCRIPT_DIR/vctl-config" -- set "$PGSQLCFG/SqlDatabaseName" "$dbname"
    "$SCRIPT_DIR/vctl-config" -- set "$PGSQLCFG/SqlHostName" "$dbserver"
    "$SCRIPT_DIR/vctl-config" -- set "$PGSQLCFG/SqlHostPort" ''
    "$SCRIPT_DIR/vctl-config" -- set "$PGSQLCFG/SqlUserName" "$dbuser"
    "$SCRIPT_DIR/vctl-config" -- set "$PGSQLCFG/SqlPassword" ''
    (
        export "VEEAM_SETUP_PGSQL_PASSWORD=$dbpasswd"
        "$SCRIPT_DIR/pgsql-addsu" "$dbuser" -p VEEAM_SETUP_PGSQL_PASSWORD
        "$SCRIPT_DIR/vctl-setup-tool" backupsavedatabaseconfiguration \
            "sqlserverdatabasename:$dbname" \
            "sqlservername:$dbserver" \
            "sqlserverlogin:$dbuser"
    )

    local -A dbentraid=(
        [server]=$dbserver
        [port]=""
        [user]=veeam-usr-pgsql-entra
        [passwd]="$("$SCRIPT_DIR/genpass" 128)"
    )
    entraid-db-opts dbentraid
    (
        export "VEEAM_SETUP_ENTRA_ID_PGSQL_PASSWORD=${dbentraid[passwd]}"
        logexec "$SCRIPT_DIR/pgsql-addrole" "${dbentraid[user]}" -p VEEAM_SETUP_ENTRA_ID_PGSQL_PASSWORD \
            -o CREATEDB -o LOGIN
        "$SCRIPT_DIR/vctl-setup-tool" entraiddbconfigurator \
            "entraidsqlservername:${dbentraid[server]}" \
            "entraidsqlserverport:${dbentraid[port]}" \
            "entraidsqlserverlogin:${dbentraid[user]}"
    )

    "$SCRIPT_DIR/vctl-setup-tool" backupdeploydatabase \
        "componentsautoupgrade:${ARGS[init/components-autoupgrade]:-}" \
        "enablelicenseautoupdate:${ARGS[init/license-autoupgrade]:-}" \
        "databasedeploymode:install"
}

init-db-remote() {
    [[ -n "${ARGS[init/db-server]:-}" ]] || die "${ARGS[prog]}: option required --db-server"

    echo "Initializing remote database"

    local dbname; local dbserver; local dbport; local dbuser; local dbpasswd;
    [[ -n ${ARGS[init/db-name]:-} ]] \
        && dbname=${ARGS[init/db-name]} \
        || dbname=$("$SCRIPT_DIR/vctl-config" get "$PGSQLCFG/SqlDatabaseName")
    dbserver="${ARGS[init/db-server]:-}"
    dbport=
    dbuser="${ARGS[init/db-user]:-}"
    dbpasswd=
    [[ -v ${ARGS[init/db-passwd]:-} ]] && local -n dbpasswd=${ARGS[init/db-passwd]};
    if [[ $dbserver =~ ^([^/].*):([1-9][0-9]{0,4})$ ]]; then
        dbserver=${BASH_REMATCH[1]}
        dbport=${BASH_REMATCH[2]}
    fi
    "$SCRIPT_DIR/vctl-config" -- set "$PGSQLCFG/SqlDatabaseName" "$dbname"
    "$SCRIPT_DIR/vctl-config" -- set "$PGSQLCFG/SqlHostName" "$dbserver"
    "$SCRIPT_DIR/vctl-config" -- set "$PGSQLCFG/SqlHostPort" "$dbport"
    "$SCRIPT_DIR/vctl-config" -- set "$PGSQLCFG/SqlUserName" "$dbuser"
    "$SCRIPT_DIR/vctl-config" -- set "$PGSQLCFG/SqlPassword" ''
    (
        export "VEEAM_SETUP_PGSQL_PASSWORD=$dbpasswd"
        "$SCRIPT_DIR/vctl-setup-tool" backupsavedatabaseconfiguration \
            "sqlserverdatabasename:$dbname" \
            "sqlservername:$dbserver" \
            "sqlserverport:$dbport" \
            "sqlserverlogin:$dbuser"
    )

    local -A dbentraid=([server]=$dbserver [port]="" [user]=$dbuser [passwd]=$dbpasswd)
    entraid-db-opts dbentraid
    (
        export "VEEAM_SETUP_ENTRA_ID_PGSQL_PASSWORD=${dbentraid[passwd]}"
        "$SCRIPT_DIR/vctl-setup-tool" entraiddbconfigurator \
            "entraidsqlservername:${dbentraid[server]}" \
            "entraidsqlserverport:${dbentraid[port]}" \
            "entraidsqlserverlogin:${dbentraid[user]}"
    )

    "$SCRIPT_DIR/vctl-setup-tool" backupdeploydatabase \
        "componentsautoupgrade:${ARGS[init/components-autoupgrade]:-}" \
        "enablelicenseautoupdate:${ARGS[init/license-autoupgrade]:-}" \
        "databasedeploymode:install"
}

init-db-update() {
    echo "Initializing database after update"
    local -a args=(
        "componentsautoupgrade:${ARGS[init/components-autoupgrade]:-}"
        "enablelicenseautoupdate:${ARGS[init/license-autoupgrade]:-}"
        "databasedeploymode:${ARGS[init/deploy-mode]:-}"
    )
    "$SCRIPT_DIR/vctl-setup-tool" backupdeploydatabase "${args[@]}"
}

init-selinux() {
    echo "Configuring SELinux"
    if ! (grep -q selinuxfs /proc/filesystems && [[ -f /etc/selinux/config ]]); then
        echo "Will skip: SELinux is disabled"
        return 0
    fi
    setenforce 1
    setsebool -P httpd_can_network_connect 1
    if ! semanage port -l | grep 'http_port_t.*13135' > /dev/null ; then
        semanage port -a -t http_port_t  -p tcp 13135
    fi
    if ! semanage port -l | grep 'http_port_t.*13136' > /dev/null ; then
        semanage port -a -t http_port_t -p tcp 13136
    fi
}

init-nginx() {
    echo "Initializing Nginx"
    # shellcheck disable=SC2046
    nginx -t $(non-verbose-arg -q)
    systemctl start nginx
    systemctl reload nginx
    systemctl enable nginx
}

init-iso() {
    local -a pgsql_args=()
    [[ -n "${ARGS[init/db-wipe]:-}" ]] && pgsql_args+=("--wipe")
    check-iso-packages
    configure-postgresql "${pgsql_args[@]}"
    cmd-install-veeamdeployment
    cmd-install-veeamtransport
    init-common
    init-db-local
    init-selinux
}

cmd-status() {
    trap-reset
    "$SCRIPT_DIR/vctl-service" status
}

cmd-start() {
    local -a a=()
    [[ -n "${ARGS[start/timeout]:-}" ]] && a+=("--timeout" "${ARGS[start/timeout]}")
    trap-reset
    "$SCRIPT_DIR/vctl-service" start
}

cmd-stop() {
    local -a a=()
    [[ -n "${ARGS[stop/timeout]:-}" ]] && a+=("--timeout" "${ARGS[stop/timeout]}")
    trap-reset
    "$SCRIPT_DIR/vctl-service" stop
}

cmd-restart() {
    local -a a=()
    [[ -n "${ARGS[restart/timeout]:-}" ]] && a+=("--timeout" "${ARGS[restart/timeout]}")
    trap-reset
    "$SCRIPT_DIR/vctl-service" restart
}

cmd-enable() { "$SCRIPT_DIR/vctl-service" enable; }

cmd-disable() { "$SCRIPT_DIR/vctl-service" disable; }

cmd-update() {
    local pkg=veeam-vbr-server
    local vercur
    local vernew=${ARGS[update/version]:-'*'}
    vercur=$(dnf -qy repoquery --qf "%{version}" --whatprovides "$pkg" --installed)
    vernew=$(dnf -qy repoquery --qf "%{version}" "$pkg-$vernew" --latest-limit 1)

    if test-version "$vercur" '>' "$vernew"; then
        die "downgrade is unsupported"
    elif test-version "$vercur" '==' "$vernew"; then
        echo "no new version is avaliable"
        return 0
    fi

    echo "Will upgrade VBR from $vercur to $vernew"

    echo "Stopping services"
    "$0" stop

    # shellcheck disable=SC2046
    "$0" $(verbose-arg -v) before-update "$vercur" "$vernew"

    echo "Updating packages"
    # shellcheck disable=SC2046
    dnf $(non-verbose-arg -q) -y install "$pkg-$vernew"

    # shellcheck disable=SC2046
    "$0" $(verbose-arg -v) init db-update --deploy-mode autoupgrade
    # shellcheck disable=SC2046
    "$0" $(verbose-arg -v) after-update "$vercur" "$vernew"

    echo "Starting services"
    "$0" start
}

cmd-before-update() {
    local vercur=${ARGS[before-update/VER-CUR]}
    local vernew=${ARGS[before-update/VER-NEW]}
    echo "Preparing system for update"
    test-version "$vercur" '<' "$vernew" || die "downgrade is unsupported"
    echo "Disabling Veeam Backup services"
    cmd-disable
    return 0
}

cmd-after-update() {
    local verold=${ARGS[after-update/VER-OLD]}
    local vercur=${ARGS[after-update/VER-CUR]}
    echo "Initializing system after update"
    test-version "$verold" '<' "$vercur" || die "downgrade is unsupported"
    test-version "$verold" '<' '13.0.1' && gpasswd -d veeam-usr-catalog veeam-grp-protection-data
    if test-version "$verold" '<=' '13.0.1.180' && [[ -f "/etc/veeam/ova_info" ]]; then
        chage -M -1 root
        chage -d -1 root
    fi
    init-common
    init-selinux
    check-iso-nginx-conf
    echo "Reloading nginx"
    systemctl reload nginx
    echo "Enabling Veeam Backup services"
    cmd-enable
    return 0
}

cmd-set-config() {
    "$SCRIPT_DIR/vctl-config" -- set "${ARGS[set-config/REGPATH]}" "${ARGS[set-config/VALUE]:-}"
}

cmd-get-config() {
    "$SCRIPT_DIR/vctl-config" -- get "${ARGS[get-config/REGPATH]}"
}

cmd-del-config() {
    "$SCRIPT_DIR/vctl-config" -- del "${ARGS[get-config/REGPATH]}"
}

cmd-gen-nginx-certs() {
    local args=(
        "/etc/veeam/certs/nginx/veeam_vbr_nginx_certificate.crt"
        -m 640
        -o "$VBRSVCUSER"
        -g "nginx"
    )
    [[ -n ${ARGS[gen-nginx-certs/force]:-} ]] && args+=(--force)
    "$SCRIPT_DIR/nginx-gencert" "${args[@]}"
}

# shellcheck disable=SC2120
cmd-install-postgresql() {
    local -a install_args=()
    [[ -n ${ARGS[install-postgresql/force]:-} ]] && install_args+=("-f")
    install_args+=("$PGSQLVER")
    "$SCRIPT_DIR/pgsql-install" "${install_args[@]}"
    configure-postgresql
}

cmd-install-veeamdeployment() {
    echo "Installing veeamdeployment"
    # shellcheck disable=SC2046
    "$SCRIPT_DIR/rpmlock" -- rpm $(non-verbose-arg --quiet) $(verbose-arg -v) --import "$INSDIR/Packages/RPM-VEEAM-PUBLICKEY"
    # shellcheck disable=SC2046
    xargs "$SCRIPT_DIR/rpmlock" -- dnf $(non-verbose-arg -q) install -y --setopt=skip_if_unavailable=true < <({
        find /opt/veeam/vbr/Packages/ -name 'veeamdeployment-*.rpm' | sort -V | tail -1
        find /opt/veeam/vbr/Packages/ -name 'veeam-openssl3-*.rpm' | sort -V | tail -1
    })
}

cmd-install-veeamtransport() {
    echo "Installing veeamtransport"
    # shellcheck disable=SC2046
    "$SCRIPT_DIR/rpmlock" -- rpm $(non-verbose-arg --quiet) $(verbose-arg -v) --import "$INSDIR/Packages/RPM-VEEAM-PUBLICKEY"
    # shellcheck disable=SC2046
    xargs "$SCRIPT_DIR/rpmlock" -- dnf $(non-verbose-arg -q) install -y --setopt=skip_if_unavailable=true < <({
        find /opt/veeam/vbr/Packages/ -name 'veeamtransport-*.rpm' | sort -V | tail -1
    })
    gpasswd -a veeam-usr-transportsvc veeam-grp-transportpriv
    systemctl restart veeamnetfsmount.service
}

cmd-install-platform-services() {
    local -a services
    mapfile -t services <<< "${ARGS[install-platform-services/ID]:-}"
    unset "services[-1]"

    local -a to_install=()
    if (( ${#services[@]} != 0 )); then
        local -A exclude=()
        local id; local name; local empty_id=; local all=
        for id in "${services[@]}"; do
            if [[ $id == 'no-'* ]]; then
                id=${id/#no-/}
                if [[ -z $id ]]; then
                    continue
                elif [[ $id = 'all' ]]; then
                    exclude[all]=1
                elif [[ -z "${PLATFORM_SERVICE[$id]:-}" ]]; then
                    continue
                else
                    name=${PLATFORM_SERVICE[$id]}
                    exclude[$name]=1
                fi
            else
                if [[ -z $id ]]; then
                    empty_id=1
                    continue
                elif [[ $id = 'all' ]]; then
                    all=1
                elif [[ -z "${PLATFORM_SERVICE[$id]:-}" ]]; then
                    die "unknown plugin id: $id"
                else
                    name=${PLATFORM_SERVICE[$id]}
                    to_install+=("$name")
                fi
            fi
        done

        if [[ -n "${exclude[all]:-}" ]]; then
            return 0
        elif [[ -n $all ]]; then
            to_install=("${PLATFORM_SERVICE[@]}")
        elif (( ${#to_install[@]} == 0 )); then
            [[ -n $empty_id ]] && return 0
            to_install=("${PLATFORM_SERVICE[@]}")
        fi

        services=("${to_install[@]}")
        to_install=()
        for name in "${services[@]}"; do
            [[ -n "${exclude[$name]:-}" ]] && continue
            to_install+=("$name")
        done
    else
        to_install=("${PLATFORM_SERVICE[@]}")
    fi
    (( ${#to_install[@]} == 0 )) && return 0

    echo "Installing platform services"
    # shellcheck disable=SC2046
    "$SCRIPT_DIR/rpmlock" -- dnf $(non-verbose-arg -q) install -y "${to_install[@]}"
}

check-iso-packages() {
    echo "Checking installed packages"
    local pkg
    for pkg in "${PLATFORM_SERVICE[@]}" \
        perl-Digest-Perl-MD5 \
        postgresql-server \
        postgresql-contrib \
        postgresql-plperl;
    do
        local -a version=()
        mapfile -t version < <(dnf -qy repoquery --qf "%{version}" --installed --whatprovides "$pkg")
        (( ${#version[@]} == 0 )) && die "$pkg not installed"
        (( ${#version[@]} != 1 )) && die "multiple versions of $pkg is installed"
        verbose-echo "found $pkg with version ${version[0]}"

        # shellcheck disable=SC2053
        if [[ $pkg = postgresql-* && ${version[0]} != $PGSQLVER ]]; then
            die "$pkg has incompatible version ${version[0]}"
        fi
    done
}

# shellcheck disable=SC2046
check-iso-nginx-conf() {
    echo "Checking nginx configuration"
    local nginx
    nginx="$(which nginx)"
    chcon $(verbose-arg -v) -t bin_t "$nginx"
    local r=0
    "$nginx" -t $(non-verbose-arg -q) || r=$?
    restorecon $(verbose-arg -v) "$nginx"
    return $r
}

entraid-db-opts() {
    local -n _opts=$1
    if [[ -z ${ARGS[init/entra-id-db-server]:-} ]]; then
        return 0
    fi
    local dbserver=${ARGS[init/entra-id-db-server]}
    local dbport=
    if [[ $dbserver != /* && $dbserver =~ (.*):([1-9][0-9]{0,4})$ ]]; then
        dbserver=${BASH_REMATCH[1]}
        dbport=${BASH_REMATCH[2]}
    fi
    _opts[server]=$dbserver
    _opts[port]=$dbport
    _opts[user]="${ARGS[init/entra-id-db-user]:-}"
    local dbpasswd=
    [[ -n ${ARGS[init/entra-id-db-pass]:-} ]] && local -n dbpasswd=${ARGS[init/entra-id-db-pass]};
    _opts[passwd]="${dbpasswd:-}"
    return 0
}

# common
configure-postgresql() {
    "$SCRIPT_DIR/pgsql-init" \
        --pgdata /var/lib/veeamdata/pgsql \
        --logdir /var/log/veeam_pgsql \
        --alias veeampgsql.service \
        --logdir-group veeam-grp-log \
        --logdir-mode 2750 \
        --logfile-mode 0640 \
        "$@"
}

main "$@"
