#!/bin/bash -e
#
# bondingadmin-setup - Perform Bondingadmin install/update tasks
#

export PATH="/bin:/sbin:/usr/bin:/usr/sbin"

BONDINGADMIN_VERSION="6.8"
BONDINGADMIN_CONF=/etc/bondingadmin/bondingadmin.conf
BONDINGADMIN_VERSION_CACHE='/var/lib/bondingadmin/version'
INFLUX_EPOCH=1      # increment by 1 on change to influxdb config
INFLUX_EPOCH_CACHE='/var/lib/bondingadmin/influx-epoch'
BONDINGADMIN_LOCAL_REPOS='/var/lib/bondingadmin/repos/'

# Indicates that a bondingadmin version has been cached, and that the install time is newer than the service start time
function _is_upgrade() {
    if [ -f $BONDINGADMIN_VERSION_CACHE ] ; then
        install_time=$(stat -c %Z /usr/lib/systemd/system/bondingadmin.service)
        start_timestamp=$(systemctl show bondingadmin | grep ExecMainStartTimestamp= | cut -d '=' -f 2)
        service_start_time=$(date -d "$start_timestamp" +%s)
        if [ "$install_time" -gt "$service_start_time" ] ; then
            return 0
        fi
    fi
    return 1
}

if _is_upgrade ; then
    IS_UPGRADE=true
else
    IS_UPGRADE=
fi

function is_upgrade() {
    test -n "$IS_UPGRADE"
}


# Indicates that the cached bondingadmin version is not current
function _is_major_upgrade() {
    if [ -f $BONDINGADMIN_VERSION_CACHE ] ; then
        if [ $(cat $BONDINGADMIN_VERSION_CACHE) != $BONDINGADMIN_VERSION ] ; then
            return 0
        fi
    fi
    return 1
}

if _is_major_upgrade ; then
    IS_MAJOR_UPGRADE=true
else
    IS_MAJOR_UPGRADE=
fi

function is_major_upgrade() {
    test -n "$IS_MAJOR_UPGRADE"
}


# Indicates that there is no bondingadmin version cached
function is_new_install() {
    test ! -f $BONDINGADMIN_VERSION_CACHE
}


# Indicates if julius has been disabled manually
function julius_disabled() {
    test -f $JULIUS_DISABLED
}


function test_postgresql_state() {
    version=$1
    desired_state=$2

    state=$(systemctl show postgresql@${version}-main.service | grep 'SubState=' | cut -d '=' -f 2)

    test "$state" = "$desired_state"
}


function wait_for_postgresql_state() {
    version=$1
    desired_state=$2

    echo "Waiting for postgresql ${version} to be ${desired_state}."
    while ! test_postgresql_state $version $desired_state ; do
        sleep 1
    done
}


function postgresql_upgrade() {
    # Check for 11 cluster and upgrade to 13
    pg_lsclusters | tail -n+2 | grep -qe '^11' && {
        pg_dropcluster --stop 13 main
        wait_for_postgresql_state 13 dead
        systemctl stop postgresql
        wait_for_postgresql_state 11 dead
        pg_upgradecluster -v 13 11 main
        pg_dropcluster 11 main
        systemctl start postgresql
        wait_for_postgresql_state 13 running
    } ||:
}


function influx_config_updated() {
    [ -f $INFLUX_EPOCH_CACHE ] && cache=$(cat $INFLUX_EPOCH_CACHE) || cache=
    if [[ $cache < $INFLUX_EPOCH ]] ; then
        return 0
    fi
    return 1
}


function set_julius_token(){
    echo "Checking API token"
    multapplied_api_token=$(ba get multapplied_api_token)
    token_set=
	while [ ! "$token_set" ] ; do
        if [ "$multapplied_api_token" ] ; then
            ba testmultappliedtoken && token_set="true" || {
                echo "Bad API token; try again"
                multapplied_api_token=""
            }
        else
            multapplied_api_token="$(get_string "Julius API token" "")"
            ba set multapplied_api_token "$multapplied_api_token"
        fi
	done
}


function api_token_exists() {
    token=$(ba get multapplied_api_token)

    if [ -z "$token" ] ; then
        return 1
    fi

    return 0
}

function get_random() {
    len=$1
    test -z $len && len=128
    pwgen -1 -s $len
}

function get_string() {
    val=""
    msg="$1"
    default="$2"
    if [ ! -z "$default" ] ; then
        msg="$msg [$default]"
    fi

    while test -z "$val" ; do
        val=$(systemd-ask-password --echo "$msg:")
        if [[ -z "$val" && ! -z "$default" ]] ; then
            val="$default"
        fi
    done
    echo "$val"
}

# get_bool <message> [default]
#
# Display message and get bool from input. If default is given, it will be
# returned if no input is entered
#
function get_bool() {
    val=""
    msg="$1 (y/n)"
    default="$2"
    if [ ! -z "$default" ] ; then
        test "$default" = "True" && default_answer="y" || default_answer="n"
        msg="$msg [$default_answer]"
    fi

    while test -z "$val" ; do
        val=$(systemd-ask-password --echo "$msg:")
        if [[ -z "$val" && ! -z "$default" ]] ; then
            val="$default_answer"
        fi
        val=${val,,}  # lowercase
        val=${val:0:1}  # first character
        if [ "$val" = y ] ; then
            val=True
        elif [ "$val" = n ] ; then
            val=False
        else
            val=
        fi
    done
    echo $val
}

function confirm_partner_conf(){
    echo -e "\nUsing the following configuration options:"
    for param in "${conf_params[@]}"; do
        echo "${conf_prompts[$param]}: ${!param}"
    done
    confirm=$(get_bool "Do you wish to continue with this configuration?" True)
    if [ "$confirm" = "False" ] ; then
        return 1
    else
        return 0
    fi
}

function get_partner_conf() {

    echo 'getting confg'
    declare conf_params=("mgmt_server_url" "full_name" "short_name" "email" "country" "province" "city")

    declare -A conf_prompts=(
        ["mgmt_server_url"]="Management server hostname"
        ["full_name"]="Full name (e.g., Internet Widgets Ltd.)"
        ["short_name"]="Short name (e.g., Internet Widgets)"
        ["email"]="Email"
        ["country"]="Two-letter country code"
        ["province"]="State or province name (full name)"
        ["city"]="City"
    )
    touch $BONDINGADMIN_CONF
    skip_configuration="True"


    if [ -s "$BONDINGADMIN_CONF" ]; then
        for param in "${conf_params[@]}"; do
            value=$(grep -Po "^$param = \K.*" $BONDINGADMIN_CONF || true)
            if [ "$param" = "mgmt_server_url" ] && [ -z "$value" ]; then
                value="$(hostname)"
            fi
            if [ -z "$value" ] ; then
                skip_configuration="False"
            fi
            declare "$param=$value"
        done
    else
        declare mgmt_server_url="$(hostname)"
        skip_configuration="False"
    fi
    if [ "$skip_configuration" = "False" ] ; then
        confirmed_conf="False"
        while [ "$confirmed_conf" = "False" ] ; do
            conf_changed="False"
            for param in "${conf_params[@]}"; do
                prompt="${conf_prompts[$param]}"
                value="$(get_string "$prompt" "${!param}")"
                if [ "$param" = "country" ]; then
                    while [[ ! "$value" =~ ^[A-Za-z]{2}$ ]]; do
                        value="$(get_string "Please enter a valid two-letter country code" "${!param}")"
                    done
                fi
                if [ ! "${!param}" == "$value" ] ; then
                    conf_changed="True"
                fi
                declare "$param=$value"
            done
            if [ "$conf_changed" == "True" ] ; then
                if confirm_partner_conf; then
                    confirmed_conf="True"
                fi
            else
                confirmed_conf="True"
            fi
        done
        echo "[partner]" > $BONDINGADMIN_CONF
        for param in "${conf_params[@]}"; do
            echo "${param} = ${!param}" >> $BONDINGADMIN_CONF
        done
    fi
}


#
# Main
#

get_partner_conf

# Set hostname
#
if [ ! -f $BONDINGADMIN_CONF ] ; then
    cat >&2 <<EOF
The bondingadmin config file is not present at $BONDINGADMIN_CONF. This can
happen if the package installation failed.

Please contact support.
EOF
    exit 1
fi

hostname=$(echo $(grep mgmt_server_url /etc/bondingadmin/bondingadmin.conf | head -n 1 | cut -d '=' -f 2))
if [ -z "$hostname" ] ; then
    echo "The mgmt_server_url option is empty in $BONDINGADMIN_CONF" >&2
    exit 1
fi
if [ "$(hostname -f)" != "$hostname" ] ; then
    echo "Updating hostname to $hostname"
    hostnamectl set-hostname $hostname
fi

# Ensure the locale is en_US.UTF-8
#
echo "Checking for valid locale"
if ! grep -q 'LANG=.*\.UTF-8' /etc/default/locale; then
    echo 'Setting default locale to en_US.UTF-8...'
    sed -i 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen
    locale-gen
    update-locale LANG="en_US.UTF-8" LANGUAGE="en_US:en"
    echo 'You MUST run locale and ensure your current settings are a UTF-8 locale, and if not, restart your session.'
    echo 'Once your locale is confirmed to be UTF-8, run bondingadmin-setup again.'
    exit 1
fi

# Remove legacy files
#
rm -rf /etc/bondingadmin/iso.d
rm -rf /var/lib/bondingadmin/preseed*


# Set up postfix
#
if [ ! -f /etc/postfix/main.cf ] ; then
    cat <<EOF | debconf-set-selections
postfix postfix/mailname string $hostname
postfix postfix/main_mailer_type string 'Internet Site'
EOF
    DEBIAN_FRONTEND=noninteractive apt-get install -f -y postfix
fi


# Create CA if it does not exist
#
if [ ! -f /var/lib/bondingadmin/ca/index ] ; then
    make-bondingadmin-ca
fi
yes y | make-server-cert
make-bondingadmin-openvpn

if is_new_install ; then
    make-https-cert
fi


# Create salt node key files if necessary
#
test ! -f /etc/bondingadmin/salt-config/states/node/root_unauthorized_keys && touch /etc/bondingadmin/salt-config/states/node/root_unauthorized_keys
test ! -f /etc/bondingadmin/salt-config/states/node/root_authorized_keys && touch /etc/bondingadmin/salt-config/states/node/root_authorized_keys

# Remove the local repo if it exists
if [ -d $BONDINGADMIN_LOCAL_REPOS ] ; then
    rm -rf $BONDINGADMIN_LOCAL_REPOS
fi


# Sysctl
#
/sbin/sysctl -p /etc/sysctl.d/firewall.conf ||:


# Tempfile config
#
/bin/systemd-tmpfiles --create


# Services
#
BONDINGADMIN_ENABLE_SERVICES="\
    aggfail.service \
    bondingadmin-collectd \
    bondingadmin-flowcollector-dnat.service \
    bondingadmin-nftables.service \
    bondingadmin-nftables-manager.path \
    bondingadmin-salt-access.service \
    bondingadmin-salt-master.service \
    bondingadmin-salt-minion.service \
    bondingadmin-uwsgi.service \
    bondingadmin.service \
    errorreporter.service \
    homestead.service \
    huey.service \
    influxmux.service \
    mgmtvpn.service \
    mgmtvpn-event-handler.service \
    nodeupdates.service \
    sync-base-isos.timer \
    bondingadmin-cleanup-config-updates.timer \
    bondingadmin-backup-remote.timer \
    bondingadmin-update-crl.timer \
    bondingadmin-push-backup.timer \
    bondingadmin-remove-old-primary-backups.timer \
    bondingadmin-update-outage-stats.timer \
    bondingadmin-salt-state-apply.timer \
"
BONDINGADMIN_DISABLE_SERVICES="\
    collectd.service \
    salt-master.service \
    uwsgi.service \
"
BONDINGADMIN_STOP_ON_UPGRADE_SERVICES="\
    bondingadmin.service \
    bondingadmin-fetch-latest-bonding-version.timer \
    bondingadmin-salt-master.service \
    sync-base-isos.timer \
"
BONDINGADMIN_RESTART_ON_UPGRADE_SERVICES="\
    bondingadmin-nftables.service \
    nginx.service \
"
BONDINGADMIN_TIMERS="\
    bondingadmin-update-crl.timer \
    bondingadmin-push-backup.timer \
    bondingadmin-backup-remote.timer \
    bondingadmin-salt-state-apply.timer \
    bondingadmin-update-outage-stats.timer \
    bondingadmin-cleanup-config-updates.timer \
    bondingadmin-remove-old-primary-backups.timer \
    bondingadmin-fetch-latest-bonding-version.timer
"

systemctl daemon-reload
systemctl disable --now $BONDINGADMIN_DISABLE_SERVICES ||:
systemctl enable $BONDINGADMIN_ENABLE_SERVICES

if is_upgrade ; then
    echo "Stopping bondingadmin services"
    systemctl stop $BONDINGADMIN_STOP_ON_UPGRADE_SERVICES
fi


# Create secrets
#
if [ ! -f "/etc/bondingadmin/django-secret-key" ]; then
    echo $(get_random) > /etc/bondingadmin/django-secret-key
fi

# Influx can only have 56 character passwords (as per bcrypt), so limit
# the get_random function to that.
if [ ! -f "/etc/bondingadmin/influx-root-pw" ]; then
    echo $(get_random 56) > /etc/bondingadmin/influx-root-pw
fi

# LEGACY-REMOVE-WHEN: databases are separated by space
if [ ! -f "/etc/bondingadmin/influx-graph-pw" ]; then
    echo $(get_random 56) > /etc/bondingadmin/influx-graph-pw
fi

# Create the password for the collectd postgresql user
if [ ! -f "/etc/bondingadmin/postgres-collectd-pw" ]; then
    echo $(get_random 63) > /etc/bondingadmin/postgres-collectd-pw
fi

if [ ! -f "/etc/bondingadmin/local-www-key" ]; then
    echo $(get_random) > /etc/bondingadmin/local-www-key
fi


# Collectd
#
COLLECTD_PASS=$(cat /etc/bondingadmin/postgres-collectd-pw)
COLLECTD_POSTGRES_TEMPLATE="/usr/share/bondingadmin/default/postgresql.conf.in"
COLLECTD_CONF_DIR="/etc/bondingadmin/collectd/collectd.conf.d"
COLLECTD_CONF_FILE="$COLLECTD_CONF_DIR/postgresql.conf"
install -d $COLLECTD_CONF_DIR
sed s/{{PASSWORD}}/$COLLECTD_PASS/ "$COLLECTD_POSTGRES_TEMPLATE" > "$COLLECTD_CONF_FILE"


# Database
#
if is_major_upgrade ; then
    echo "Check database upgrade"
    postgresql_upgrade

    # Fix missing flowcollection migration record if necessary
    # Remove this once 6.8 is fully deployed
    cat << EOF | su - postgres -c "psql bondingadmin"
        INSERT INTO django_migrations (app, name, applied)
        SELECT 'flowcollection', '0005_change_space_fk_on_delete_to_protect', now()
            WHERE NOT EXISTS (
                SELECT 1
                FROM django_migrations
                WHERE name = '0005_change_space_fk_on_delete_to_protect'
            )
            AND EXISTS (
                SELECT 1
                FROM django_migrations
                WHERE name = '0006_remove_previous_nfprobe_maxflows_max_value'
            );
EOF
fi

if is_new_install || is_upgrade ; then
    echo "Performing database migrations"
    /usr/share/bondingadmin/initdb.sh

    echo "Initializing needed database objects"
    ba initialize_objects

    echo "Saving persistent object configurations"
    ba update_configs
fi


# Set Julius
#
if [ ! -f $JULIUS_DISABLED ] ; then
    set_julius_token
fi


# Laywire
#
laywire-management-setup


# Salt
#
if is_new_install || is_upgrade ; then
    echo "Updating salt pillars"
    ba update_salt_pillars

    echo "Configuring bondingadmin-salt-master"
    /usr/share/bondingadmin/initsalt.sh
fi


# Check for major version upgrade
#
if is_major_upgrade ; then
    echo "Major upgrade"
    echo "Resetting update message"
    ba reset_seen_update_message
fi


# Remember that this version was installed
#
echo "Caching bondingadmin version"
echo $BONDINGADMIN_VERSION > $BONDINGADMIN_VERSION_CACHE


# Nginx
#
rm -f /etc/nginx/sites-enabled/default
ln -sf /etc/nginx/sites-available/bondingadmin /etc/nginx/sites-enabled/bondingadmin

# Create the default influx proxy conf if missing
if [ ! -f "/etc/bondingadmin/influxdb_proxy.conf" ]; then
    echo "proxy_pass http://127.0.0.1:8086/;" > /etc/bondingadmin/influxdb_proxy.conf
fi

# Set the download srv if not set
if [ ! -f /etc/bondingadmin/nginx/download.conf ]; then
    echo 'set $download download.multapplied.net;' > /etc/bondingadmin/nginx/download.conf
fi

# Set the Nginx resolver address if not set
if [ ! -f /etc/bondingadmin/nginx/resolver.conf ]; then
    echo 'resolver 1.1.1.1;' > /etc/bondingadmin/nginx/resolver.conf
fi


# Services restart and get latest bonding version
#
if is_new_install || is_upgrade ; then
    echo "Starting bondingadmin services"
    systemctl restart $BONDINGADMIN_STOP_ON_UPGRADE_SERVICES $BONDINGADMIN_RESTART_ON_UPGRADE_SERVICES

    echo "Fetch bonding version"
    if [ -n "$BONDING_RELEASE" ] ; then
        ba set repository_release $BONDING_RELEASE
    fi
    ba update_bonding_version
    systemctl start bondingadmin-fetch-latest-bonding-version.service
fi

echo "Starting Timers"
systemctl enable --now $BONDINGADMIN_TIMERS


# InfluxDB
#
mkdir -p /var/log/influxdb
chown -R influxdb:influxdb /var/log/influxdb

# Write a sensible influxdb configuration file (combines our settings with the default settings)
/usr/bin/influxd config -config /etc/bondingadmin/influxdb/bondingadmin.conf > /etc/influxdb/bondingadmin.conf

if influx_config_updated ; then
    echo "Setting up influxdb"
    /usr/lib/bondingadmin/influxconfig
    echo "Caching influxdb epoch"
    echo $INFLUX_EPOCH > $INFLUX_EPOCH_CACHE
fi


# Julius
#
if api_token_exists ; then
    # Sync pricing labels
    echo "Syncing pricing labels"
    ba sync_pricing_labels || echo "Cannot sync pricing labels."

    echo -e "Setting up API user for Julius"
    ba setup_julius_api_user
    echo -e "Setting up OAuth"
    ba fetch_oauth_client_authentication

    # Clear ISOs for old spaces and regenerate new ones for valid spaces
    echo "Regenerating the bonding ISO images"
    ba regenerate_isos || echo "Cannot regenerate bonding ISO images."

    # Check and send ssh public key used for backups
    ba sendmgmtpublickey || echo "Cannot send management public key."
else
    echo "To finish setup, please set the API token (ba set multapplied_api_token) and try again."
fi


# Backups
#
if is_new_install; then
    echo -e "Setting up backups"
    backup-bondingadmin
    ba remotebackup
    ba remotebackupstatus
fi
