#!/bin/bash

# inspiration from: https://linuxcommand.org/lc3_adv_dialog.php
#                   https://www.foxinfotech.in/2019/04/linux-dialog-examples.html


# Define the dialog exit status codes
: ${DIALOG_OK=0}
: ${DIALOG_CANCEL=1}
: ${DIALOG_HELP=2}
: ${DIALOG_EXTRA=3}
: ${DIALOG_ITEM_HELP=4}
: ${DIALOG_ESC=255}

applianceVersion='?.?.?'
if [ -f /etc/appliance_version ]
then
    applianceVersion=`cat /etc/appliance_version`
fi
applianceIP=`ip address show dev eth0 | grep -w inet | awk '{print $2}' | sed "s/\/.*//"`
umask 002

export ILM_TUI="ACTIVE"
if [ "x$ANSIBLE_CONFIG" == "x" ]
then
   export ANSIBLE_CONFIG=/etc/ilm-ansible/ansible.cfg
fi

backTitle="ILM Appliance ($applianceVersion; $applianceIP)"
mainMenu=(
    "network"      "Configure network parameters"
    'ingressTLS'   "Configure ingress TLS certificates"
    'trustedCA'    "Configure custom trusted certificates"
    'postgres'     "Configure database"
    'dockerRepo'   "Configure Docker repository access credentials"
    'mail'         "Configure email server used for notifications"
    'config'       "Configure ILM"
    'install'      "Install ILM"
    'status'       "Show ILM status"
    'advanced'     "Advanced options"
    'exit'         "Exit ILM manager"
)
advancedMenu=(
#    'testNetwork'        'Verify network access',
#    'installPSQL'        'Install only postgress',
#    'verifyPSQL'         'Verify access to postgress database'
#    'installHelm'        'Install only helm utility'
#    'installRKE2'        "Install only RKE2 - Rancher\'s next-generation Kubernetes distribution"
#    'verifyRKE2'         'Verify kubernetes',
    'update'             'Update Operating System'
    'removeC'            'Remove ILM'
    'installC'           'Install only ILM'
    'remove'             'Remove RKE2 & ILM'
    'versions'           'Show ILM images versions'
    'shell'              'Enter system shell'
    'reboot'             'Reboot system'
    'shutdown'           'Shutdown system'
    'exit'               "Exit advanced menu"
)
networkMenu=(
    'hostname'     "Configure hostname"
    'network'      "Configure network parameters"
    'proxyHTTP'    "Configure HTTP proxy parameters"
    'timeSync'     "Configure NTP parameters"
    'exit'         "Exit network menu"
)
networkSettings='/etc/ilm-ansible/vars/network.yml'
proxySettings='/etc/ilm-ansible/vars/proxy.yml'
ntpSettings='/etc/ilm-ansible/vars/ntp.yml'
dockerSettings='/etc/ilm-ansible/vars/docker.yml'
ilmSettings='/etc/ilm-ansible/vars/ilm.yml'
databaseSettings='/etc/ilm-ansible/vars/database.yml'
ingressSettings='/etc/ilm-ansible/vars/ingress.yml'
trustedCASettings='/etc/ilm-ansible/vars/trustedCA.yml'
emailSettings='/etc/ilm-ansible/vars/email.yml'
# Not settings, read only: the list of the components which are pulled from
# the private repository lives with the role installing them, so that marking
# one here and checking it there can never disagree.
ilmRoleDefaults='/etc/ilm-ansible/roles/ilm/defaults/main.yml'
# The mail server check, the same one the playbook runs before it installs the
# email notification provider, so what is accepted here installs there.
smtpCheck='/usr/bin/ilm-smtp-check'
rkeUninstall='/usr/local/bin/rke2-uninstall.sh'
kubectl='/var/lib/rancher/rke2/bin/kubectl'

maxRemoveWait=360

tmpF=`mktemp /tmp/ilm-manager.XXXXXX`
#trap "rm $tmpF 2>/dev/null" 0 1 2 5 15

# Rows and COLS are exact terminal size
read Rows COLS < <(stty size)
# eRows and eRows are efective terminal size - subctracting some
# values to get nice output.
eRows=$[$Rows-3]
eCOLS=$[$COLS-8]

# Add spaces to left of string to make it centered on actual terminal
# size.
center_text () {
    len=`echo -n "$1" | wc -c`
    printf "%*s\n" $(( ($len + $COLS) / 2 )) "$1"
}

# Calculate size of menu.
#
# Needed size is counted as number of menu options with addition of 3
# lines on top and 4 on bottom. If needed size is higher than avaialbe
# efective terminal size than efective size of termial is used.
max_menu_rows() {
    array=("$@")

    needed=$[${#array[@]}/2+3+4]

    if [ $needed -gt $eRows ]
    then
        echo $eRows
    else
        echo $needed
    fi
}

allParametersRequired() {
    dialog --backtitle "$backTitleCentered" --title 'Error[!]' --msgbox "All parameters are required." 10 50
}

errorMessage() {
    dialog --backtitle "$backTitleCentered" --title 'Error[!]' --msgbox "$1" 10 50
}

infoMessage() {
    dialog --backtitle "$backTitleCentered" --title 'Info[!]' --msgbox "$1" 10 50
}

infoText() {
    temp=`mktemp /tmp/ilm-manager.textbox.XXXXXX`
    echo "$1" > $temp
    dialog --backtitle "$backTitleCentered" --title 'Info' --textbox "$temp" $eRows $eCOLS
    rm $temp
}

confirm() {
    text=$1

    dialog --backtitle "$backTitleCentered" --title "Confirmation" --yesno "$text" 10 50
    return_value=$?

    if [ $return_value == $DIALOG_OK ]
    then
        return 0
    else
        return 1
    fi
}

removeALL() {
    local p=$FUNCNAME

    clear -x
    if [ -e $rkeUninstall ]
    then
        logger "$p: calling $rkeUninstall"
        sudo $rkeUninstall
        echo ""
        echo "RKE2 & ILM removed, press enter key to return to menu"
        read
    else
        echo "RKE2 not present ($rkeUninstall), press enter key to return to menu"
        read
    fi
}

removeILM() {
    local p=$FUNCNAME

    clear -x
    if [ -e $kubectl ]
    then
        cmd="$kubectl delete ns ilm"
        logger "$p: calling $cmd"
        sudo $cmd
        echo ""
        echo "ILM removed. Waiting for kubernetes to complete cleaning."

        sudo /usr/bin/rm /root/install/docker-secret.yaml
        sudo /usr/bin/rm /root/install/values.local.yaml

        cleaned=0
        start=`date "+%s"`
        while true
        do
            if `sudo $kubectl -n ilm get all 2>&1 | grep 'No resources found' >/dev/null 2>&1`
            then
                cleaned=0
                logger "$p: ilm name space deleted"
                echo "DONE"
                break
            else
                diff=$[`date "+%s"`-$start]
                if [ $diff -gt $maxRemoveWait ]
                then
                    msg="waiting take to long!"
                    logger "$p: $msg"
                    echo "$msg"
                    echo ""
                    sudo $kubectl -n ilm get all
                    echo ""
                    echo "Kubernetes failled to remove ilm namespace."
                    break
                else
                    logger "$p: waiting ${diff}s ..."
                    echo "waiting ${diff}s ..."
                    sleep 10
                fi
            fi
        done
        echo ""
        echo "Press enter key to return to menu."
        read
    else
        echo "$kubectl is missing. Is RKE2 installed?"
        echo ""
        echo "Press enter key to return to menu."
        read
    fi
}

update() {
    clear -x
    echo ""
    old_sum=`sha1sum $0 | sed "s/ .*$//"`
    sudo /usr/bin/apt update && sudo /usr/bin/apt upgrade
    new_sum=`sha1sum $0 | sed "s/ .*$//"`
    echo ""
    if [ "$new_sum" = "$old_sum" ]
    then
        logger "$p: upgrade finished and TUI was not changed"
    else
        logger "$p: upgrade finished and TUI WAS CHANGED"
        echo "ILM TUI was upgraded during system upgrade. It is recomended
to close this session and re-loging back into appliance.
"
    fi
    echo "press enter to return into menu"
    read
}

backTitleCentered=`center_text "$backTitle"`;

advancedMenu() {
    local p=$FUNCNAME
    # duplicate (make a backup copy of) file descriptor 1 on descriptor 3
    exec 3>&1

    advancedMenuRows=`max_menu_rows "${advancedMenu[@]}"`
    logger "$p: menuRows = $advancedMenuRows";
    result=$(dialog --backtitle "$backTitleCentered" \
            --ok-label 'Select' \
            --cancel-label 'Exit' \
            --menu "advanced menu" $advancedMenuRows $eCOLS $advancedMenuRows \
            "${advancedMenu[@]}" 2>&1 1>&3)
    # get dialog's exit status
    return_value=$?
    # close file descriptor 3
    exec 3>&-

    logger "$p: return_value=$return_value, result='$result'"

    if [ $return_value != $DIALOG_OK ]
    then
        logger "$p: not OK => exiting"
        return 1
    fi

    case $result in
    'exit')
        logger "$p: exit"
        echo "exit"
        return 1
        ;;
    'update')
        update
        ;;
    'remove')
        if confirm "Remove RKE2 (kubernetes) including ILM? Database will remain untouched."
        then
            logger "$p: complete remove confirmed"
            removeALL
        else
            logger "$p: complete remove canceled"
        fi
        ;;
    'removeC')
        if confirm "Remove ILM installation? Database will remain untouched."
        then
            logger "$p: complete remove confirmed"
            removeILM
        else
            logger "$p: complete remove canceled"
        fi
        ;;
    'installC')
        logger "$p: installing ILM only"
        execAnsible \
            "sudo /usr/bin/ansible-playbook /etc/ilm-ansible/playbooks/ilm.yml --tags ilm"
        logger "$p: installing ILM only"
        ;;
    'versions')
        versions
        ;;
    'shell')
        clear -x
        echo "to exit from shell and return into menu type 'exit'"
        /bin/bash --login
        echo "press enter key to return into menu"
        read
        ;;
    'reboot')
        clear -x
        echo "rebooting"
        sudo /sbin/shutdown -r now
        sleep 1000
        ;;
    'shutdown')
        clear -x
        echo "stoping system"
        sudo /sbin/shutdown -h now
        sleep 1000;
        ;;
    *)
        dialog --backtitle "$backTitleCentered" --title " not implemented " --msgbox "Option \"$result\" is not implemented." 8 $eCOLS
        logger "$p: result=$result is not implemented";
        ;;
    esac

    return 0
};

networkMenu() {
    local p=$FUNCNAME
    # duplicate (make a backup copy of) file descriptor 1 on descriptor 3
    exec 3>&1

    networkMenuRows=`max_menu_rows "${networkMenu[@]}"`
    logger "$p: menuRows = $networkMenuRows";
    result=$(dialog --backtitle "$backTitleCentered" \
            --ok-label 'Select' \
            --cancel-label 'Exit' \
            --menu "network menu" $networkMenuRows $eCOLS $networkMenuRows \
            "${networkMenu[@]}" 2>&1 1>&3)
    # get dialog's exit status
    return_value=$?
    # close file descriptor 3
    exec 3>&-

    logger "$p: return_value=$return_value, result='$result'"

    if [ $return_value != $DIALOG_OK ]
    then
        logger "$p: not OK => exiting"
        return 1
    fi

    case $result in
    'hostname')
        changeHostname
        ;;
    'network')
        network
        ;;
    'proxyHTTP')
        proxyHTTP
        ;;
    'timeSync')
        timeSync
        ;;
    'exit')
        logger "$p: exit"
        echo "exit"
        return 1
        ;;
    *)
        dialog --backtitle "$backTitleCentered" --title " not implemented " --msgbox "Option \"$result\" is not implemented." 8 $eCOLS
        logger "$p: result=$result is not implemented";
        ;;
    esac

    return 0
};

validIPv4() {
    local ip="$1"
    err_msg='IP address is invalid'
    [[ "$ip" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]] || { return 1; }
    for i in ${ip//./ }; do
        [[ "${#i}" -gt 1 && "${i:0:1}" == 0 ]] && { return 2; }
        [[ "$i" -gt 255 ]] && { return 3; }
    done
    return 0
}

network () {
    maxLen=120
    maxInputLen=$[$eCOLS-20]
    local p=$FUNCNAME
    settings=$networkSettings

    usedhcp=$(tf2x $(grep < $settings '^ *usedhcp: ' | sed "s/^ *usedhcp: *//"))
    address=`grep < $settings '^ *address: ' | sed "s/^ *address: *//"`
    netmask=`grep < $settings '^ *netmask: ' | sed "s/^ *netmask: *//"`
    gateway=`grep < $settings '^ *gateway: ' | sed "s/^ *gateway: *//"`
    searchdomain=`grep < $settings '^ *searchdomain: ' | sed "s/^ *searchdomain: *//"`
    nameservers=`grep < $settings -A 1000 '^ *nameservers:'| grep '^ * -' | sed "s/^ *- *//" | tr "\n" "," | sed "s/, *$//"`

    dialog --backtitle "$backTitleCentered" --title " Network parameters " \
       --form "\nLeave all fields empty if you want networking to be untouched.\nUse comma as a separator for DNS." 15 $eCOLS 6 \
       "Use DHCP: "      1 6 "$usedhcp"      1 16 2 1 \
       "Address: "       2 7 "$address"      2 16 16 15 \
       "Netmask: "       3 7 "$netmask"      3 16 16 15 \
       "Gateway: "       4 7 "$gateway"      4 16 16 15 \
       "Search domain: " 5 0 "$searchdomain" 5 16 16 15 \
       "Nameservers: "   6 1 "$nameservers"  6 16 $maxInputLen $maxLen \
       2>$tmpF
    # get dialog's exit status
    return_value=$?

    if [ $return_value != $DIALOG_OK ]
    then
        logger "$p: dialog not OK => returing without any change"
        return 1
    fi

    cat $tmpF | sed "s/^ //gm" | sed "s/ $//gm" | {
        read -r _usedhcp
        read -r _address
        read -r _netmask
        read -r _gateway
        read -r _searchdomain
        read -r _nameservers

        lines=`cat $tmpF | sed "s/^ //gm" | sed "s/ $//gm" | grep -v '^$' | wc -l`

        logger "$p: Use DHCP      '$usedhcp' => '$_usedhcp'"
        logger "$p: Address       '$address' => '$_address'"
        logger "$p: Netmask       '$netmask' => '$_netmask'"
        logger "$p: Gateway       '$gateway' => '$_gateway'"
        logger "$p: Search domain '$searchdomain' => '$_searchdomain'"
        logger "$p: Nameservers   '$nameservers' => '$_nameservers'"

        newSettings=`mktemp /tmp/ilm-manager.network.XXXXXX`

        _usedhcp=$(x2tf "$_usedhcp")

        if [ -n "${_address}${_netmask}${_gateway}" ]
        then
            # DHCP is not active and something is written in basic network config -> test values
            someInvalid=0

            if ! validIPv4 "$_address"
            then
                msg="Provided IP address: '$_address' is invalid."
                logger "$p: $msg"
                errorMessage "$msg"
                someInvalid=1
            fi

            if ! validIPv4 "$_netmask"
            then
                msg="Provided Netmask: '$_netmask' is invalid."
                logger "$p: $msg"
                errorMessage "$msg"
                someInvalid=1
            fi

            if ! validIPv4 "$_gateway"
            then
                msg="Provided Gateway: '$_gateway' is invalid."
                logger "$p: $msg"
                errorMessage "$msg"
                someInvalid=1
            fi

            if [ $someInvalid -gt 0 ]
            then
                return 1;
            fi
        fi

        logger "$_nameservers"
        if [ $lines -gt 0 ]
        then
            echo "---
network:" > $newSettings
            [ "$_usedhcp"        != '' ] && echo "  usedhcp: $_usedhcp" >> $newSettings
            [ "$_address"        != '' ] && echo "  address: $_address" >> $newSettings
            [ "$_netmask"        != '' ] && echo "  netmask: $_netmask" >> $newSettings
            [ "$_searchdomain"   != '' ] && echo "  searchdomain: $_searchdomain" >> $newSettings
            [ "$_gateway"        != '' ] && echo "  gateway: $_gateway" >> $newSettings
            if [ "$_nameservers" != '' ]
            then
                OFS=$IFS
                IFS=','
                echo "  nameservers:" >> $newSettings
                read -ra no <<< "$_nameservers"
                for ip in "${no[@]}"; do
                    logger "$i"
                    if ! validIPv4 $ip
                    then
                        msg="Provided DNS nameserver address: '$ip' is invalid."
                        logger "$p: $msg"
                        errorMessage "$msg"
                        return 1;
                    fi
                    echo "    - $ip" >> $newSettings
                done
                IFS=$OFS
            fi

            if `diff $newSettings $settings >/dev/null 2>&1`
            then
                logger "$p: nothing changed"
                rm $newSettings
            else
                cp $newSettings $settings
                rm $newSettings
                logger "$p: settings changed $settings rewritten"
                execAnsible \
                    "sudo /usr/bin/ansible-playbook /etc/ilm-ansible/playbooks/ilm.yml --tags network" \
                    "network"
                logger "$p: ansible finished"
            fi
        else
            logger "$p: all parameters are empty - zeroizing $settings"
            cp /dev/null $settings
        fi
    }
}

proxyHTTP() {
    maxLen=120
    maxInputLen=$[$eCOLS-20]
    local p=$FUNCNAME
    settings=$proxySettings

    httpProxy=`grep < $settings '^ *http: ' | sed "s/^ *http: *//"`
    httpsProxy=`grep < $settings '^ *https: ' | sed "s/^ *https: *//"`
    ftpProxy=`grep < $settings '^ *ftp: ' | sed "s/^ *ftp: *//"`
    ftpsProxy=`grep < $settings '^ *ftps: ' | sed "s/^ *ftps: *//"`
    noProxy=`grep < $settings -A 1000 '^ *dont_use_for:'| grep '^ * -' | sed "s/^ *- *//" | tr "\n" "," | sed "s/, *$//"`

    dialog --backtitle "$backTitleCentered" --title " HTTP proxy " \
       --form "Provide parameters of proxy server required for Internet access" 15 $eCOLS 5 \
       "HTTP_PROXY:"  1 2 "$httpProxy"  1 14 $maxInputLen $maxLen \
       "HTTPS_PROXY:" 2 1 "$httpsProxy" 2 14 $maxInputLen $maxLen \
       "FTP_PROXY:"   3 3 "$ftpProxy"   3 14 $maxInputLen $maxLen \
       "FTPS_PROXY:"  4 2 "$ftpsProxy"  4 14 $maxInputLen $maxLen \
       "NO_PROXY:"    5 4 "$noProxy"    5 14 $maxInputLen $maxLen \
       2>$tmpF
    # get dialog's exit status
    return_value=$?

    if [ $return_value != $DIALOG_OK ]
    then
        logger "$p: dialog not OK => returing without any change"
        return 1
    fi

    cat $tmpF | sed "s/^ //gm" | sed "s/ $//gm" | {
    read -r _httpProxy
    read -r _httpsProxy
    read -r _ftpProxy
    read -r _ftpsProxy
    read -r _noProxy

    lines=`cat $tmpF | sed "s/^ //gm" | sed "s/ $//gm" | grep -v '^$' | wc -l`

    logger "$p: httpProxy  '$httpProxy' => '$_httpProxy'"
    logger "$p: httpsProxy '$httpsProxy' => '$_httpsProxy'"
    logger "$p: ftpProxy   '$ftpProxy' => '$_ftpProxy'"
    logger "$p: ftpsProxy  '$ftpsProxy' => '$_ftpsProxy'"
    logger "$p: noProxy    '$noProxy' => '$_noProxy'"

    newSettings=`mktemp /tmp/ilm-manager.proxy.XXXXXX`

    if [ $lines -gt 0 ]
    then
        echo "---
proxy:" > $newSettings
        [ "$_httpProxy" != '' ] && echo "  http: $_httpProxy" >> $newSettings
        [ "$_httpsProxy" != '' ] && echo "  https: $_httpsProxy" >> $newSettings
        [ "$_ftpProxy" != '' ] && echo "  ftp: $_ftpProxy" >> $newSettings
        [ "$_ftpsProxy" != '' ] && echo "  ftps: $_ftpsProxy" >> $newSettings
        if [ "$_noProxy" != '' ]
        then
            OFS=$IFS
            IFS=','
            echo "  dont_use_for:" >> $newSettings
            read -ra no <<< "$_noProxy"
            for i in "${no[@]}"; do
                echo "    - $i" >> $newSettings
            done
            IFS=$OFS
        fi

        if `diff $newSettings $settings >/dev/null 2>&1`
        then
            logger "$p: nothing changed"
            rm $newSettings
        else
            cp $newSettings $settings
            rm $newSettings
            logger "$p: settings changed $settings rewritten"
            execAnsible \
                "sudo /usr/bin/ansible-playbook /etc/ilm-ansible/playbooks/ilm.yml --tags http_proxy" \
                "http_proxy"
            logger "$p: ansible finished"
            dialog --backtitle "$backTitleCentered" --title "Warning" --msgbox "If you are changing HTTP Proxy parameters before the first ILM installation, you have to close this terminal session and re-login back to make changes effective.

    If you are updating the existing installation, you have first reboot the appliance and then re-run the installation process to get the setting correctly propagated." 13 50
        fi
    else
        logger "$p: all parameters are empty - zeroizing $settings"
        cp /dev/null $settings
    fi
    }
}

timeSync() {
    maxLen=120
    maxInputLen=$[$eCOLS-20]
    local p=$FUNCNAME
    settings=$ntpSettings

    timezone=`grep < $settings '^ *timezone: ' | sed "s/^ *timezone: *//"`
    servers=`grep < $settings -A 1000 '^ *servers:'| grep '^ * -' | sed "s/^ *- *//" | tr "\n" "," | sed "s/, *$//"`

    dialog --backtitle "$backTitleCentered" --title " NTP servers " \
       --form "\nLeave all fields empty if you want time setting to be untouched.\n\nUse comma as a separator for timeservers and no spaces.\n" 15 $eCOLS 2 \
       "Timezone:"       1 1 "$timezone"      1 14 $maxInputLen $maxLen \
       "NTP servers:"    2 1 "$servers"    2 14 $maxInputLen $maxLen \
       2>$tmpF
    # get dialog's exit status
    return_value=$?

    if [ $return_value != $DIALOG_OK ]
    then
        logger "$p: dialog not OK => returing without any change"
        return 1
    fi

    cat $tmpF | sed "s/^ //gm" | sed "s/ $//gm" | {
    read -r _timezone
    read -r _servers

    lines=`cat $tmpF | sed "s/^ //gm" | sed "s/ $//gm" | grep -v '^$' | wc -l`

    logger "$p: timezone   '$timezone' => '$_timezone'"
    logger "$p: servers '$servers' => '$_servers'"

    newSettings=`mktemp /tmp/ilm-manager.ntp.XXXXXX`

    if [ $lines -gt 0 ]
    then
        echo "---
ntp:" > $newSettings
        [ "$_timezone" != '' ] && echo "  timezone: $_timezone" >> $newSettings
        if [ "$_servers" != '' ]
        then
            OFS=$IFS
            IFS=','
            echo "  servers:" >> $newSettings
            read -ra no <<< "$_servers"
            for i in "${no[@]}"; do
                echo "    - $i" >> $newSettings
            done
            IFS=$OFS
        fi

        if `diff $newSettings $settings >/dev/null 2>&1`
        then
            logger "$p: nothing changed"
            rm $newSettings
        else
            cp $newSettings $settings
            rm $newSettings
            logger "$p: settings changed $settings rewritten"
            execAnsible \
                "sudo /usr/bin/ansible-playbook /etc/ilm-ansible/playbooks/ilm.yml --tags ntp" \
                "ntp"
            logger "$p: ansible finished"
        fi
    else
        logger "$p: all parameters are empty - zeroizing $settings"
        cp /dev/null $settings
    fi
    }
}

database() {
    maxLen=120
    maxInputLen=$[$eCOLS-20]
    local p=$FUNCNAME
    settings=$databaseSettings

    username=`grep < $settings '^ *username: ' | sed "s/^ *username: *//"`
    password=`grep < $settings '^ *password: ' | sed "s/^ *password: *//"`
    database=`grep < $settings '^ *database: ' | sed "s/^ *database: *//"`
    repository=`grep < $settings '^ *repository: ' | sed "s/^ *repository: *//"`
    version=`grep < $settings '^ *version: ' | sed "s/^ *version: *//"`

    dialog --backtitle "$backTitleCentered" --title " local PostgreSQL " \
       --form "Parameters of PostgreSQL running on appliance" 13 $eCOLS 5 \
       "username:"   1 1 "$username"   1 14 $maxInputLen $maxLen \
       "password:"   2 1 "$password"   2 14 $maxInputLen $maxLen \
       "database:"   3 1 "$database"   3 14 $maxInputLen $maxLen \
       "repository:" 4 1 "$repository" 4 14 $maxInputLen $maxLen \
       "version:"    5 1 "$version"    5 14 $maxInputLen $maxLen \
       2>$tmpF
    # get dialog's exit status
    return_value=$?

    if [ $return_value != $DIALOG_OK ]
    then
        logger "$p: dialog not OK => returing without any change"
        return 1
    fi

    cat $tmpF | sed "s/^ //gm" | sed "s/ $//gm" | {
    read -r _username
    read -r _password
    read -r _database
    read -r _repository
    read -r _version

    lines=`cat $tmpF | sed "s/^ //gm" | sed "s/ $//gm" | grep -v '^$' | wc -l`

    logger "$p: username   '$username'   => '$_username'"
    logger "$p: password   '$password'   => '$_password'"
    logger "$p: database   '$database'   => '$_database'"
    logger "$p: repository '$repository' => '$_repository'"
    logger "$p: version    '$version'    => '$_version'"

    newSettings=`mktemp /tmp/ilm-manager.database.XXXXXX`

    # Permit version to be empty. This is usefull when using PostgreSQL from debian,
    # which is the default. On Debian there is only one version of database available
    # so explicit settings make no sense.
    if [ $lines -eq 5 ] || ( [ $lines -eq 4 ] && [ $_version == ''] )
    then
        echo "---
postgres:" > $newSettings
        [ "$_username"   != '' ] && echo "  username:   $_username"   >> $newSettings
        [ "$_password"   != '' ] && echo "  password:   $_password"   >> $newSettings
        [ "$_database"   != '' ] && echo "  database:   $_database"   >> $newSettings
        [ "$_repository" != '' ] && echo "  repository: $_repository" >> $newSettings
        [ "$_version"    != '' ] && echo "  version:    $_version"    >> $newSettings

        if `diff $newSettings $settings >/dev/null 2>&1`
        then
            logger "$p: nothing changed"
            rm $newSettings
        else
            cp $newSettings $settings
            rm $newSettings
            logger "$p: settings changed $settings rewritten"
        fi
    else
        logger "$p: some parameters are missing - refusing to continue"
        allParametersRequired
    fi
    }
}

# true|false to X
tf2x() {
    in=$1
    local p=$FUNCNAME

    if echo "$in" | grep -i true >/dev/null 2>&1
    then
        echo "X"
        logger "$p: '$in' -> 'X'"
    else
        echo ""
        logger "$p: '$in' -> ' '"
    fi

    return 0
}

# Asterisk for a component pulled from the private repository, nothing for
# the rest. $privateComponents is read from the defaults of the ilm role,
# which is also what the playbook checks the credentials against.
privateMark() {
    key=$1

    case " $privateComponents " in
        *" $key "*)
            echo '*'
            ;;
        *)
            echo ''
            ;;
    esac
}

# X to true|false
x2tf() {
    in=$1
    local p=$FUNCNAME

    if [ "$in" != 'X' ] && [ "$in" != 'x' ] && [ "$in" != 'y' ]
    then
        echo 'false'
        logger "$p: '$in' -> 'false'"
    else
        echo 'true'
        logger "$p: '$in' -> 'true'"
    fi

}

# Ask the mail server whether it accepts the settings, with the very tool the
# playbook runs before it installs the email notification provider. That
# provider connects and authenticates while its context starts, so settings it
# refuses do not degrade a feature - they leave the pod in CrashLoopBackOff and
# the installation waiting out its helm timeout. Returns 0 when the server is
# happy, otherwise echoes what it answered, the way testCertificateKey() does.
testSMTP() {
    local host=$1
    local port=$2
    local tls=$3
    local username=$4
    local password=$5
    local p=$FUNCNAME
    local emsg
    local args=(--host "$host" --timeout 10)

    [ "$port" != '' ] && args+=(--port "$port")

    if [ "$tls" == 'true' ]
    then
        args+=(--tls)
    else
        args+=(--no-tls)
    fi

    [ "$username" != '' ] && args+=(--user "$username")

    # The password goes through the environment, so that it stays out of the
    # process list. Only stderr is wanted, stdout is the success message.
    if emsg=$(SMTP_PASSWORD="$password" $smtpCheck "${args[@]}" 2>&1 >/dev/null)
    then
        logger "$p: $host accepted the settings"
        return 0
    fi

    logger "$p: $host refused the settings: $emsg"
    echo "$emsg"
    return 1
}

# Whether vars/email.yml names a mail server at all. mail.example.com is what
# the package ships, it does not resolve, and the playbook refuses it as well.
mailServerConfigured() {
    local host=`grep < $emailSettings '^ *smtp_host: ' | sed "s/^ *smtp_host: *//"`

    [ "$host" != '' ] && [ "$host" != 'mail.example.com' ]
}

emailConfig() {
    local maxLen=120
    local maxInputLen=$[$eCOLS-20]
    local p=$FUNCNAME
    # local, unlike the other screens: ilmConfig calls this one and would
    # otherwise carry on writing the ilm settings into email.yml.
    local settings=$emailSettings
    local newSettings

    smtp_host=`grep < $settings '^ *smtp_host: ' | sed "s/^ *smtp_host: *//"`
    smtp_port=`grep < $settings '^ *smtp_port: ' | sed "s/^ *smtp_port: *//"`
    smtp_tls=$(tf2x $(grep < $settings '^ *smtp_tls: ' | sed "s/^ *smtp_tls: *//"))
    smtp_username=`grep < $settings '^ *smtp_username: ' | sed "s/^ *smtp_username: *//"`
    smtp_password=`grep < $settings '^ *smtp_password: ' | sed "s/^ *smtp_password: *//"`

    # The form comes back with what was typed when the mail server refuses the
    # settings, so the fields have to survive being read - hence the redirect
    # below where the other screens pipe into a subshell and lose them.
    while true
    do
        dialog --backtitle "$backTitleCentered" --title " SMTP configuration " \
           --form "Parameters of email server used for notifications" 17 $eCOLS 6 \
           "Hostname:"           1 1 "$smtp_host"      1 15 $maxInputLen $maxLen \
           "Port number:"        2 1 "$smtp_port"      2 15 $maxInputLen $maxLen \
           "Use TLS:"            3 1 "$smtp_tls"       3 15 2            1 \
           "Username:"           4 1 "$smtp_username"  4 15 $maxInputLen $maxLen \
           "Password:"           5 1 "$smtp_password"  5 15 $maxInputLen $maxLen \
           2>$tmpF
        # get dialog's exit status
        return_value=$?

        if [ $return_value != $DIALOG_OK ]
        then
            logger "$p: dialog not OK => returing without any change"
            return 1
        fi

        {
        read -r _smtp_host
        read -r _smtp_port
        read -r _smtp_tls
        read -r _smtp_username
        read -r _smtp_password
        } < <(sed "s/^ //gm" $tmpF | sed "s/ $//gm")

        logger "$p: smtp_host      '$smtp_host'     => '$_smtp_host'"
        logger "$p: smtp_port      '$smtp_port'     => '$_smtp_port'"
        logger "$p: smtp_tls       '$smtp_tls'      => '$_smtp_tls'"
        logger "$p: smtp_username  '$smtp_username' => '$_smtp_username'"

        _smtp_tls=$(x2tf "$_smtp_tls")

        # what the form shows on the next round
        smtp_host=$_smtp_host
        smtp_port=$_smtp_port
        smtp_tls=$(tf2x "$_smtp_tls")
        smtp_username=$_smtp_username
        smtp_password=$_smtp_password

        if [ "$_smtp_host" == '' ]
        then
            errorMessage "Hostname of the mail server is required."
            continue
        fi

        emsg=$(testSMTP "$_smtp_host" "$_smtp_port" "$_smtp_tls" "$_smtp_username" "$_smtp_password") && break

        dialog --backtitle "$backTitleCentered" --title 'Error[!]' \
           --yesno "The mail server did not accept these settings:

$emsg

Keep them anyway? ILM will not install while they are refused." 15 $eCOLS

        [ $? == $DIALOG_OK ] && break
    done

    newSettings=`mktemp /tmp/ilm-manager.email.XXXXXX`

    echo "---" > $newSettings
    [ "$_smtp_host" != '' ]     && echo "smtp_host: $_smtp_host" >> $newSettings
    [ "$_smtp_port" != '' ]     && echo "smtp_port: $_smtp_port" >> $newSettings
    [ "$_smtp_tls" != '' ]      && echo "smtp_tls: $_smtp_tls" >> $newSettings
    [ "$_smtp_username" != '' ] && echo "smtp_username: $_smtp_username" >> $newSettings
    [ "$_smtp_password" != '' ] && echo "smtp_password: $_smtp_password" >> $newSettings

    if `diff $newSettings $settings >/dev/null 2>&1`
    then
        logger "$p: nothing changed"
        rm $newSettings
    else
        cp $newSettings $settings
        rm $newSettings
        logger "$p: settings changed $settings rewritten"
    fi
}

ilmConfig() {
    maxLen=20
    maxInputLen=$[$eCOLS-20]
    local p=$FUNCNAME
    settings=$ilmSettings

    version=`grep < $settings '^ *version: ' | sed "s/^ *version: *//"`
    cbomRepository=$(tf2x $(grep < $settings '^ *cbomRepository: ' | sed "s/^ *cbomRepository: *//"))
    commonCredentialProvider=$(tf2x $(grep < $settings '^ *commonCredentialProvider: ' | sed "s/^ *commonCredentialProvider: *//"))
    ejbcaNgConnector=$(tf2x $(grep < $settings '^ *ejbcaNgConnector: ' | sed "s/^ *ejbcaNgConnector: *//"))
    pymsAdcsConnector=$(tf2x $(grep < $settings '^ *pymsAdcsConnector: ' | sed "s/^ *pymsAdcsConnector: *//"))
    hashicorpVaultConnector=$(tf2x $(grep < $settings '^ *hashicorpVaultConnector: ' | sed "s/^ *hashicorpVaultConnector: *//"))
    x509ComplianceProvider=$(tf2x $(grep < $settings '^ *x509ComplianceProvider: ' | sed "s/^ *x509ComplianceProvider: *//"))
    cryptosenseDiscoveryProvider=$(tf2x $(grep < $settings '^ *cryptosenseDiscoveryProvider: ' | sed "s/^ *cryptosenseDiscoveryProvider: *//"))
    ctLogsDiscoveryProvider=$(tf2x $(grep < $settings '^ *ctLogsDiscoveryProvider: ' | sed "s/^ *ctLogsDiscoveryProvider: *//"))
    networkDiscoveryProvider=$(tf2x $(grep < $settings '^ *networkDiscoveryProvider: ' | sed "s/^ *networkDiscoveryProvider: *//"))
    keystoreEntityProvider=$(tf2x $(grep < $settings '^ *keystoreEntityProvider: ' | sed "s/^ *keystoreEntityProvider: *//"))
    softwareCryptographyProvider=$(tf2x $(grep < $settings '^ *softwareCryptographyProvider: ' | sed "s/^ *softwareCryptographyProvider: *//"))
    keycloak=$(tf2x $(grep < $settings '^ *keycloak: ' | sed "s/^ *keycloak: *//"))
    utilsService=$(tf2x $(grep < $settings '^ *utilsService: ' | sed "s/^ *utilsService: *//"))
    emailProvider=$(tf2x $(grep < $settings '^ *emailProvider: ' | sed "s/^ *emailProvider: *//"))
    webhookNotificationProvider=$(tf2x $(grep < $settings '^ *webhookNotificationProvider: ' | sed "s/^ *webhookNotificationProvider: *//"))
    externalAuthorityProvider=$(tf2x $(grep < $settings '^ *externalAuthorityProvider: ' | sed "s/^ *externalAuthorityProvider: *//"))
    otpkiConnector=$(tf2x $(grep < $settings '^ *otpkiConnector: ' | sed "s/^ *otpkiConnector: *//"))
    timestampFormattingConnector=$(tf2x $(grep < $settings '^ *timestampFormattingConnector: ' | sed "s/^ *timestampFormattingConnector: *//"))

    # Which components are pulled from the private repository. Read out of the
    # defaults of the ilm role, the same list the playbook refuses to install
    # them without credentials by - a plain sed range, the 'grep -A 1000'
    # idiom used for the lists in vars/ would run past the end of the block.
    privateComponents=`sed -n '/^ilm_private_components:/,/^[^ #-]/p' $ilmRoleDefaults \
        | sed -n 's/^ *- *//p' | tr '\n' ' '`
    logger "$p: components of the private repository: $privateComponents"

    # dialog --form require parameters height width formheight
    #  * at this moment we need 19 parameters from user
    #  * height = 0 means the smallest possible window size
    #  * formheight is how many of them are visible at once, the form scrolls
    #    when there are more, so keep it within the terminal - the two lines
    #    of the prompt grow the window as well, hence -6
    #
    formHeight=19
    [ $formHeight -gt $[$eRows-6] ] && formHeight=$[$eRows-6]

    dialog --backtitle "$backTitleCentered" --title " ILM configuration " \
       --form "Parameters of ILM installation\n* needs credentials for the private image repository." 0 $eCOLS $formHeight \
       "ILM version:"              1 1 "$version"                       1 21 $maxInputLen $maxLen \
       "CBOM Repository$(privateMark cbomRepository):"                                2 1 "$cbomRepository"                 2 35 2            1 \
       "Common Credential Provider$(privateMark commonCredentialProvider):"           3 1 "$commonCredentialProvider"       3 35 2            1 \
       "Cryptosense Discovery Provider$(privateMark cryptosenseDiscoveryProvider):"   4 1 "$cryptosenseDiscoveryProvider"   4 35 2            1 \
       "CT logs Discovery Provider$(privateMark ctLogsDiscoveryProvider):"            5 1 "$ctLogsDiscoveryProvider"        5 35 2            1 \
       "EJBCA NG Connector$(privateMark ejbcaNgConnector):"                           6 1 "$ejbcaNgConnector"               6 35 2            1 \
       "email Provider$(privateMark emailProvider):"                                  7 1 "$emailProvider"                  7 35 2            1 \
       "External Authority Provider$(privateMark externalAuthorityProvider):"         8 1 "$externalAuthorityProvider"      8 35 2            1 \
       "HashiCorp Vaul Connector$(privateMark hashicorpVaultConnector):"              9 1 "$hashicorpVaultConnector"        9 35 2            1 \
       "Keycloak IdP$(privateMark keycloak):"                                        10 1 "$keycloak"                      10 35 2            1 \
       "Keystore Entity Provider$(privateMark keystoreEntityProvider):"              11 1 "$keystoreEntityProvider"        11 35 2            1 \
       "Network Discovery Provider$(privateMark networkDiscoveryProvider):"          12 1 "$networkDiscoveryProvider"      12 35 2            1 \
       "OTPKI Connector$(privateMark otpkiConnector):"                               13 1 "$otpkiConnector"                13 35 2            1 \
       "py MS ADCS Connector$(privateMark pymsAdcsConnector):"                       14 1 "$pymsAdcsConnector"             14 35 2            1 \
       "Software Cryptography Provider$(privateMark softwareCryptographyProvider):"  15 1 "$softwareCryptographyProvider"  15 35 2            1 \
       "Timestamp Formatting Connector$(privateMark timestampFormattingConnector):"  16 1 "$timestampFormattingConnector"  16 35 2            1 \
       "Utils Service$(privateMark utilsService):"                                   17 1 "$utilsService"                  17 35 2            1 \
       "webhook Notification Provider$(privateMark webhookNotificationProvider):"    18 1 "$webhookNotificationProvider"   18 35 2            1 \
       "X509 Compliance Provider$(privateMark x509ComplianceProvider):"              19 1 "$x509ComplianceProvider"        19 35 2            1 \
       2>$tmpF
    # get dialog's exit status
    return_value=$?

    if [ $return_value != $DIALOG_OK ]
    then
        logger "$p: dialog not OK => returing without any change"
        return 1
    fi

    # read selected components from temporary file written by dialog. The
    # redirect below reads them in this shell, where a pipe would read them in
    # a subshell - the mail server check further down opens the SMTP screen,
    # which writes over $tmpF while a pipe would still be reading it.
    {
    read -r _version
    read -r _cbomRepository
    read -r _commonCredentialProvider
    read -r _cryptosenseDiscoveryProvider
    read -r _ctLogsDiscoveryProvider
    read -r _ejbcaNgConnector
    read -r _emailProvider
    read -r _externalAuthorityProvider
    read -r _hashicorpVaultConnector
    read -r _keycloak
    read -r _keystoreEntityProvider
    read -r _networkDiscoveryProvider
    read -r _otpkiConnector
    read -r _pymsAdcsConnector
    read -r _softwareCryptographyProvider
    read -r _timestampFormattingConnector
    read -r _utilsService
    read -r _webhookNotificationProvider
    read -r _x509ComplianceProvider
    } < <(sed "s/^ //gm" $tmpF | sed "s/ $//gm")

    logger "$p: version                      '$version'                      => '$_version'"
    logger "$p: cbomRepository               '$cbomRepository'               => '$_cbomRepository'"
    logger "$p: commonCredentialProvider     '$commonCredentialProvider'     => '$_commonCredentialProvider'"
    logger "$p: cryptosenseDiscoveryProvider '$cryptosenseDiscoveryProvider' => '$_cryptosenseDiscoveryProvider'"
    logger "$p: ctLogsDiscoveryProvider      '$ctLogsDiscoveryProvider'      => '$_ctLogsDiscoveryProvider'"
    logger "$p: ejbcaNgConnector             '$ejbcaNgConnector'             => '$_ejbcaNgConnector'"
    logger "$p: emailProvider                '$emailProvider'                => '$_emailProvider'"
    logger "$p: externalAuthorityProvider    '$externalAuthorityProvider'    => '$_externalAuthorityProvider'"
    logger "$p: hashicorpVaultConnector      '$hashicorpVaultConnector'      => '$_hashicorpVaultConnector'"
    logger "$p: keycloak                     '$keycloak'                     => '$_keycloak'"
    logger "$p: keystoreEntityProvider       '$keystoreEntityProvider'       => '$_keystoreEntityProvider'"
    logger "$p: networkDiscoveryProvider     '$networkDiscoveryProvider'     => '$_networkDiscoveryProvider'"
    logger "$p: otpkiConnector               '$otpkiConnector'               => '$_otpkiConnector'"
    logger "$p: pymsAdcsConnector            '$pymsAdcsConnector'            => '$_pymsAdcsConnector'"
    logger "$p: softwareCryptographyProvider '$softwareCryptographyProvider' => '$_softwareCryptographyProvider'"
    logger "$p: timestampFormattingConnector '$timestampFormattingConnector' => '$_timestampFormattingConnector'"
    logger "$p: utilsService                 '$utilsService'                 => '$_utilsService'"
    logger "$p: webhookNotificationProvider  '$webhookNotificationProvider'  => '$_webhookNotificationProvider'"
    logger "$p: x509ComplianceProvider       '$x509ComplianceProvider'       => '$_x509ComplianceProvider'"

    newSettings=`mktemp /tmp/ilm-manager.ilm.XXXXXX`

    _cbomRepository=$(x2tf               "$_cbomRepository")
    _commonCredentialProvider=$(x2tf     "$_commonCredentialProvider")
    _cryptosenseDiscoveryProvider=$(x2tf "$_cryptosenseDiscoveryProvider")
    _ctLogsDiscoveryProvider=$(x2tf      "$_ctLogsDiscoveryProvider")
    _ejbcaNgConnector=$(x2tf             "$_ejbcaNgConnector")
    _emailProvider=$(x2tf                "$_emailProvider")
    _externalAuthorityProvider=$(x2tf    "$_externalAuthorityProvider")
    _hashicorpVaultConnector=$(x2tf      "$_hashicorpVaultConnector")
    _keycloak=$(x2tf                     "$_keycloak")
    _keystoreEntityProvider=$(x2tf       "$_keystoreEntityProvider")
    _networkDiscoveryProvider=$(x2tf     "$_networkDiscoveryProvider")
    _otpkiConnector=$(x2tf               "$_otpkiConnector")
    _pymsAdcsConnector=$(x2tf            "$_pymsAdcsConnector")
    _softwareCryptographyProvider=$(x2tf "$_softwareCryptographyProvider")
    _timestampFormattingConnector=$(x2tf "$_timestampFormattingConnector")
    _utilsService=$(x2tf                 "$_utilsService")
    _webhookNotificationProvider=$(x2tf  "$_webhookNotificationProvider")
    _x509ComplianceProvider=$(x2tf       "$_x509ComplianceProvider")

    # The email notification provider connects to the mail server and
    # authenticates while it starts, so switching it on without one configured
    # installs a pod that does nothing but crash, and the playbook refuses to
    # get that far. Offer the SMTP screen instead of writing a setting that
    # cannot install.
    if [ "$_emailProvider" == 'true' ] && ! mailServerConfigured
    then
        logger "$p: emailProvider enabled without a mail server"
        errorMessage "email Provider sends through a mail server, and none is configured yet. The next screen asks for it."
        emailConfig

        if ! mailServerConfigured
        then
            logger "$p: still no mail server, leaving emailProvider off"
            errorMessage "No mail server configured, email Provider stays switched off."
            _emailProvider='false'
        fi
    fi

    echo "---
ilm:
  version: $_version
  cbomRepository: $_cbomRepository
  commonCredentialProvider: $_commonCredentialProvider
  cryptosenseDiscoveryProvider: $_cryptosenseDiscoveryProvider
  ctLogsDiscoveryProvider: $_ctLogsDiscoveryProvider
  ejbcaNgConnector: $_ejbcaNgConnector
  emailProvider: $_emailProvider
  externalAuthorityProvider: $_externalAuthorityProvider
  hashicorpVaultConnector: $_hashicorpVaultConnector
  keycloak: $_keycloak
  keystoreEntityProvider: $_keystoreEntityProvider
  networkDiscoveryProvider: $_networkDiscoveryProvider
  otpkiConnector: $_otpkiConnector
  pymsAdcsConnector: $_pymsAdcsConnector
  softwareCryptographyProvider: $_softwareCryptographyProvider
  timestampFormattingConnector: $_timestampFormattingConnector
  utilsService: $_utilsService
  webhookNotificationProvider: $_webhookNotificationProvider
  x509ComplianceProvider: $_x509ComplianceProvider
" > $newSettings

    if `diff $newSettings $settings >/dev/null 2>&1`
    then
        logger "$p: nothing changed"
        rm $newSettings
    else
        cp $newSettings $settings
        rm $newSettings
        logger "$p: settings changed $settings rewritten"
    fi
}

testCertificateKey() {
    cert=$1
    key=$2
    local p=$FUNCNAME

    if [ ! -e $cert ]
    then
        emsg="$cert doesn't exists"
        logger "$p: $emsg"
        echo $emsg
        return 1
    fi

    if [ ! -e $key ]
    then
        emsg="$key doesn't exists"
        logger "$p: $emsg"
        echo $emsg
        return 1
    fi

    if ! key_modulus=`openssl rsa -noout -modulus -in $key`
    then
        emsg="unable to get modulus of private key in $key"
        logger "$p: $emsg"
        echo $emsg
        return 1
    fi

    # with certificate it is more complicated, there might be more
    # than one folowing awk block prints modulus of all of them
    if awk -v cmd='openssl x509 -noout -modulus' '/BEGIN/{close(cmd)}; {print | cmd}' < $cert | grep "$key_modulus" > /dev/null
    then
        logger "$p: match"
        # modulus of the key found in modulus of certs in $cert
        return 0
    fi

    emsg="private key doesn't match provided certificate"
    logger "$p: $emsg"
    echo $emsg
    return 1
}

ingressTLS() {
    maxLen=120
    maxInputLen=$[$eCOLS-20]
    local p=$FUNCNAME
    settings=$ingressSettings

    certificate=`grep < $settings '^ *certificate_file: ' | sed "s/^ *certificate_file: *//"`
    private_key=`grep < $settings '^ *private_key_file: ' | sed "s/^ *private_key_file: *//"`

    logger "$p: $settings";

    dialog --backtitle "$backTitleCentered" --title " TLS certificate " \
       --form "Certificate & unencrypted private key for incoming HTTPS" 10 $eCOLS 3 \
       "certificate:"  1 1 "$certificate" 1 14 $maxInputLen $maxLen \
       "private key:"  2 1 "$private_key" 2 14 $maxInputLen $maxLen \
       2>$tmpF
    # get dialog's exit status
    return_value=$?

    if [ $return_value != $DIALOG_OK ]
    then
        logger "$p: dialog not OK => returing without any change"
        return 1
    fi

    cat $tmpF | sed "s/^ //gm" | sed "s/ $//gm" | {
    read -r _certificate
    read -r _private_key

    lines=`cat $tmpF | sed "s/^ //gm" | sed "s/ $//gm" | grep -v '^$' | wc -l`

    logger "$p: certificate  '$certificate' => '$_certificate'"
    logger "$p: private_key  '$private_key' => '$_private_key'"

    newSettings=`mktemp /tmp/ilm-manager.ingress.XXXXXX`

    if [ "$_certificate" != '' ] && [ "$_private_key" != '' ]
    then
        if [[ "$_certificate" != /* ]]
        then
            errorMessage "Certificate path must be an absolute path (e.g. /home/ilm/czertainly.crt)."
        elif [[ "$_private_key" != /* ]]
        then
            errorMessage "Private key path must be an absolute path (e.g. /home/ilm/czertainly.key)."
        elif testRes=$(testCertificateKey "$_certificate" "$_private_key")
        then
        echo "---
ingress:
  certificate_file: $_certificate
  private_key_file: $_private_key
" >> $newSettings

        if `diff $newSettings $settings >/dev/null 2>&1`
        then
            logger "$p: nothing changed"
            rm $newSettings
        else
            cp $newSettings $settings
            rm $newSettings
            logger "$p: settings changed $settings rewritten"
        fi
        else
        # testCertificateNotOK
        logger "$p: test not OK $testRes";
        errorMessage "$testRes"
        fi
    else
        logger "$p: all parameters are empty - zeroizing $settings"
        cp /dev/null $settings
    fi
    }
}

testTrustedCAfile() {
    certs=$1
    local p=$FUNCNAME

    logger "$p: $certs";

    if [ ! -e $certs ]
    then
        emsg="unable to read $certs"
        logger "$p: $emsg"
        echo $emsg
        return 1
    fi

    cnt=`cat $certs | grep -v '^#' | awk -v cmd='openssl x509 -noout -subject ' '/BEGIN/{close(cmd)}; {print | cmd}' | wc -l`

    if [ $cnt -gt 0 ]
    then
        logger "$p: found more than 0 certs ($cnt)"
        return 0
    fi

    emsg="unable to parse $certs";
    logger "$p: $emsg"
    echo $emsg
    return 1
}

trustedCA() {
    maxLen=120
    maxInputLen=$[$eCOLS-20]
    local p=$FUNCNAME
    settings=$trustedCASettings

    trustedCA=`grep < $settings '^ *trustedCA_file: ' | sed "s/^ *trustedCA_file: *//"`

    dialog --backtitle "$backTitleCentered" --title " Trusted CAs " \
       --form "Provide path to file with aditional trusted certificates" 10 $eCOLS 3 \
       "trustedCAs:"  1 1 "$trustedCA" 1 14 $maxInputLen $maxLen \
       2>$tmpF
    # get dialog's exit status
    return_value=$?

    if [ $return_value != $DIALOG_OK ]
    then
        logger "$p: dialog not OK => returing without any change"
        return 1
    fi

    cat $tmpF | sed "s/^ //gm" | sed "s/ $//gm" | {
    read -r _trustedCA

    lines=`cat $tmpF | sed "s/^ //gm" | sed "s/ $//gm" | grep -v '^$' | wc -l`

    logger "$p: trustedCA '$trustedCA' => '$_trustedCA'"

    newSettings=`mktemp /tmp/ilm-manager.trustedCA.XXXXXX`

    if [ "$_trustedCA" != '' ]
    then
        if testRes=$(testTrustedCAfile "$_trustedCA")
        then
            trustedCAText=`mktemp /tmp/ilm-manager.XXXXXX`

            cat $_trustedCA | grep -v '^#' | awk 'BEGIN {
    cmd="openssl x509 -noout -subject -issuer -dates -serial -nameopt RFC2253,-esc_msb"
    cert="";
    in_cert=0
    first_cert=1
}
/BEGIN CERTIFICATE/ {
    cert=$0;
    in_cert=1;
    next
}
in_cert {
    cert = cert "\n" $0
}
/END CERTIFICATE/ {
    in_cert=0;
    if (first_cert) {
        first_cert=0
    } else {
        print ""
    }
    print cert | cmd;
    close(cmd);
}' > $trustedCAText
	    rows=`cat $trustedCAText | wc -l`
	    rows=$[$rows+5]
	    min=$(( $rows < $eRows ? $rows : $eRows ))
            dialog --backtitle "$backTitleCentered" --title " Trusted CAs preview " --exit-label 'OK' --textbox $trustedCAText $rows $eCOLS
            rm $trustedCAText

            echo "---
    trustedCA_file: $_trustedCA
    " >> $newSettings

            if `diff $newSettings $settings >/dev/null 2>&1`
            then
                logger "$p: nothing changed"
                rm $newSettings
            else
                cp $newSettings $settings && rm $newSettings
                logger "$p: settings changed $settings rewritten"
            fi
        else
            # testCertificateNotOK
            logger "$p: test not OK $testRes";
            errorMessage "$testRes"
        fi
    else
        logger "$p: THE ONLY ONE parameter IS empty - zeroizing $settings"
        cp /dev/null $settings
    fi
    }
}

changeHostname() {
    # https://wiki.debian.org/Hostname
    maxLen=120
    maxInputLen=$[$eCOLS-8]
    local p=$FUNCNAME

    hostname=`hostname -f`

    dialog --backtitle "$backTitleCentered" --title " System hostname " \
       --form "\nWARNING: Changing the hostname will reboot the system.\n\nProvide fully qualified hostname. It has to be all lowercase and starting with a letter." 12 $eCOLS 1 \
       ""  1 1 "$hostname" 1 2 $maxInputLen $maxLen \
       2>$tmpF
    # get dialog's exit status
    return_value=$?

    if [ $return_value != $DIALOG_OK ]
    then
        logger "$p: dialog not OK => returing without any change"
        return 1
    fi

    cat $tmpF | sed "s/^ //gm" | sed "s/ $//gm" | tr '[A-Z]' '[a-z'] | {
        read -r _hostname

        lines=`cat $tmpF | sed "s/^ //gm" | sed "s/ $//gm" | grep -v '^$' | wc -l`

        logger "$p: hostname '$hostname' => '$_hostname'"

        newSettings=`mktemp /tmp/ilm-manager.trustedCA.XXXXXX`

        if [ "x$_hostname" != 'x' ] && [ "x$hostname" != "x$_hostname" ]
        then
            if sudo /usr/bin/ilm-update-hostname "$_hostname"
            then
                infoMessage "After hostname change reboot is required. Press OK to reboot"
                clear -x
                echo "Rebooting..."
                sudo /sbin/shutdown -r now
                sleep 360
            else
                logger "$p: ilm-update-hostname failed"
                errorMessage "Hostname update failed."
            fi
        else
            logger "$p: hostname not changed"
        fi
    }
}

docker() {
    maxLen=120
    maxInputLen=$[$eCOLS-20-7]
    local p=$FUNCNAME
    settings=$dockerSettings

    username=`grep < $settings '^ *username: ' | sed "s/^ *username: *//"`
    password=`grep < $settings '^ *password: ' | sed "s/^ *password: *//"`
    server=`grep < $settings '^ *server: ' | sed "s/^ *server: *//"`
    secret=`grep < $settings '^ *secret: ' | sed "s/^ *secret: *//"`
    email=`grep < $settings '^ *email: ' | sed "s/^ *email: *//"`

    dialog --backtitle "$backTitleCentered" --title " docker repository " \
       --form "Parameters of Docker image repository" 10 $eCOLS 4 \
       "server:"               1 13 "$server"   1 21 $maxInputLen $maxLen \
       "username:"             2 11 "$username" 2 21 $maxInputLen $maxLen \
       "password:"             3 11 "$password" 3 21 $maxInputLen $maxLen \
       "email:"                4 11 "$email"    4 21 $maxInputLen $maxLen \
       2>$tmpF
    # get dialog's exit status
    return_value=$?

    if [ $return_value != $DIALOG_OK ]
    then
        logger "$p: dialog not OK => returing without any change"
        return 1
    fi

    cat $tmpF | sed "s/^ //gm" | sed "s/ $//gm" | {
        read -r _server
        read -r _username
        read -r _password
        read -r _email

        lines=`cat $tmpF | sed "s/^ //gm" | sed "s/ $//gm" | grep -v '^$' | wc -l`

        logger "$p: username  '$username' => '$_username'"
        logger "$p: password  '$password' => '$_password'"
        logger "$p: server    '$server' => '$_server'"
        logger "$p: email     '$email' => '$_email'"

        newSettings=`mktemp /tmp/ilm-manager.docker.XXXXXX`

        echo "---
docker:" > $newSettings
        [ "$_server"   != '' ] && echo "  server: $_server"     >> $newSettings
        [ "$_email"    != '' ] && echo "  email: $_email"       >> $newSettings
        [ "$secret"    != '' ] && echo "  secret: $secret"      >> $newSettings
        [ "$_username" != '' ] && echo "  username: $_username" >> $newSettings
        [ "$_password" != '' ] && echo "  password: $_password" >> $newSettings

        if `diff $newSettings $settings >/dev/null 2>&1`
        then
            logger "$p: nothing changed"
            rm $newSettings
        else
            cp $newSettings $settings
            rm $newSettings
            logger "$p: settings changed $settings rewritten"
        fi
    }
}

execAnsible() {
    local p=$FUNCNAME
    cmd=$1
    mode=$2

    logger "executing ansible: $cmd; in mode: $mode"
    clear -x
    echo "Executing Ansible:"
    echo "  $cmd"
    echo ""
    if [ "x$mode" == 'xfull-install' ]
    then
        echo "First installation takes about 15 minutes, please be patient."
    fi

    $cmd
    result=$?

    if [ $result == 0 ]
    then
        echo "Ansible finished successfully, result code: $result"
        if [ "x$mode" == 'xfull-install' ]
        then
            echo ""
            echo "Status of Kubernetes and ILM"
            /usr/bin/ilm-status
        fi
    else
        echo "Ansible failed with error code: $result".
        echo ""
        echo "Error is very likely described in output above. If you need
    to contact support please provide content of file /var/log/ansible.log"
        echo ""
    fi

    echo "press enter key to continue"
    read < /dev/tty
}

status() {
    clear -x
    echo ""
    /usr/bin/ilm-status
    echo ""
    echo "press enter to return into menu"
    read
}

versions() {
    clear -x
    echo ""
    /usr/bin/ilm-versions --detailed
    echo ""
    echo "press enter to return into menu"
    read
}


main() {
    # duplicate (make a backup copy of) file descriptor 1 on descriptor 3
    exec 3>&1

    menuRows=`max_menu_rows "${mainMenu[@]}"`
    result=$(dialog --backtitle "$backTitleCentered" \
            --ok-label 'Select' \
            --no-cancel \
            --menu "main menu" $menuRows $eCOLS $menuRows \
            "${mainMenu[@]}" 2>&1 1>&3)
    # get dialog's exit status
    return_value=$?
    # close file descriptor 3
    exec 3>&-

    logger "main_menu: return_value=$return_value, result='$result'"

    if [ $return_value != $DIALOG_OK ]
    then
        logger "main_menu: not OK => terminating"
        exit 1
    fi

    case $result in
    'exit')
        logger "main_menu: exit"
        clear -x
        exit 0
        ;;
    'network')
        logger "main_menu: network";
        while true;
        do
            networkMenu
            aRet=$?
            logger "main_menu: network menu returned=$aRet"
            if [ $aRet == 1 ];
            then
                logger "main_menu: exiting network menu";
                break
            fi
        done
        ;;
    'postgres')
        database
        ;;
    'dockerRepo')
        docker
        ;;
    'ingressTLS')
        ingressTLS
        ;;
    'trustedCA')
        trustedCA
        ;;
    'config')
        ilmConfig
        ;;
    'mail')
        emailConfig
        ;;
    'install')
        dockerServer=`grep < $dockerSettings '^ *server: ' | sed "s/^ *server: *//"`
        if confirm "Have you set up access credentials to ${dockerServer} and possibly adjusted other options? Please confirm installation by selecting Yes button. Complete installation takes about 15 minutes."
        then
        logger "$p: install confirmed"
        execAnsible \
            "sudo /usr/bin/ansible-playbook /etc/ilm-ansible/playbooks/ilm.yml" \
            "full-install"
        else
            logger "$p: install canceled"
        fi
        ;;
    'status')
        status
        ;;
    'advanced')
        logger "main_menu: advanced";
        while true;
        do
            advancedMenu
            aRet=$?
            logger "main_menu: advanced menu returned=$aRet"
            if [ $aRet == 1 ];
            then
                logger "main_menu: exiting advanced menu";
                break
            fi
        done
        ;;
    *)
        dialog --backtitle "$backTitleCentered" --title " not implemented " --msgbox "Option \"$result\" is not implemented." 8 $eCOLS
        logger "main_menu: result=$result is not implemented";
        ;;
    esac
}

while true; do main; done
