#!/usr/bin/env bash
RED='\033[0;31m'
GRN='\033[0;32m'
ORNG='\033[0;33m'
NC='\033[0m' # No Color
tmpfile=$(mktemp /tmp/setp.XXXXXX)
tmpSettingsFile=$(mktemp /tmp/settings.XXXXXX)
tmpDockerFile=$(mktemp /tmp/docker.XXXXXX)
tmpAppList=$(mktemp /tmp/applist.XXXXXX)

function finish (){
    echo -e ${NC}
    rm -rf ${tmpfile}
    rm -rf ${tmpSettingsFile}
    rm -rf ${tmpDockerFile}
    rm -rf ${tmpAppList}
}

trap finish EXIT

prodRepository="matsapps/production"
if [ $# -ne 2 ]; then
    echo "$0 - wrong number of params - usage: $0 image app  (e.g. matsapps/production:tagname blrs)"
    exit 1
fi
image=$1
app=$2
# are we in an install area (where the tar was unbundled)
if [[ ! -d "./web"  ]]; then
    echo -e "${RED}I don't see a ./web directory - I cannot set up anything, try cd'ing to the container deployment directory - exiting ${NC}"
    exit 1
fi

if [[ ! -d "./bin"  ]]; then
    echo -e "${RED}I don't see a ./bin directory - I cannot set up anything, try cd'ing to the container deployment directory - exiting ${NC}"
    exit 1
fi

if [[ ! -d "./settings"  ]]; then
    echo -e "${RED}Creating a ./settings directory${NC}"
    mkdir settings
fi

if [[ ! -d "${HOME}/mongodata"  ]]; then
    echo -e "${RED}Creating a ${HOME}/mongodata directory${NC}"
    mkdir ${HOME}/mongodata
else
    rm -rf ${HOME}/mongodata/*
fi
DEPLOYDIR=$(pwd)

cat <<%END > ./README
This program will help you configure your MATS tool suite environment based on one of the standard environments.
To do this you must have access to the internet.
This program will query the GSD appProduction service which deployment environments are currently
supported for assisted deployment. Then it will ask a series of questions in this order...
1) It will ask you which environment you wish to deploy. Choose an environment by number. The setup will ask the GSD server which versions
of the apps for your selected deployment are currently up to date.
2) The program will ask you to verify the domainName of your deployment server. It uses this to configure its own proxy service that fronts the apps.
3) It will prompt you for the fully qualified proxy hostName, defaulting to the domainName. If your public URL is the same as your domainName just accept the default. Your
deployment might be behind a redirection proxy with redirection to a different path than what is actually on the host - for example, at GSD our mats server might
be deployed on a server with an internal URL "https://something.subnet.esrl.noaa.gov" with the landing page location "/", while the public URL is a proxy redirect
"https://www.esrl.noaa.gov/gsd/mats". In this case the fully qualified proxy hostName is www.esrl.noaa.gov and the proxy_prefix_path needs to be set to "/gsd/mats".
4) Next you will be prompted for a proxy redirection path. This allows you to serve a different path than "/".
5) Finally you will be prompted you for database credentials for each role required by each app.
Database roles are usually some combination of meta_data - the database that contains app metadata,
sums_data - the database that contains an apps statistical partial sum data, model_data - a database that
contains metadata about data sources, and possibly sites_data - which contains non standard domain data.
The credentials that you provide are stored in a ${DEPLOYDIR}/settings directory with the structure ${DEPLOYDIR}/settings/appreference/settings.json. This directory will NOT
be publicly accessible. If you are familiar with this directory and the settings.json files you can make changes with an editor and the setup program
will use the existing values as defaults.
The settings files also hold the proxy_prefix_path from 4).
The program configures this deployment directory, creating a ${DEPLOYDIR}/docker-compose.yml configuration file,
and a ${DEPLOYDIR}/traefik.toml file, a suitable ${DEPLOYDIR}/web/index.html as a homepage,  and utilities such as up, down and monitoring scripts in the
${DEPLOYDIR}/bin directory.
You must have docker, docker-compose and jq installed, and you must be running this script from ${DEPLOYDIR}.
You must be running this program as a user that can run docker - do not run this script as root, in fact do not run any containers as root.
After once configuring an app you will have to confirm overwriting the old configuration if you ever re-run setup.

The reverse proxy interface has a user/password pre-set to user "admin" and password "adminpassword", you should change this by following the instructions in the traefik.toml file.
This program will create a settings directory here in this directory and a mongodata directory in the $HOME of this user if they do not already exist.

You need to acquire an SSL cert for your domain and put the certificate in /etc/ssl/certs directory of this host. If you have authority over the DNS entry for
your server you can use LetsEncrypt for no cost certs. The instructions for doing this are at https://docs.traefik.io/configuration/acme/.
If you cannot acquire a certificate you can enable the apps temporarily by commenting out
#        [[entryPoints.https.tls.certificates]]
#        certFile = "/etc/ssl/certs/mats-meteor.crt"
#        keyFile = "/etc/ssl/certs/mats-meteor.key"
in the traefik.toml file.

The toolsuite entrypoints are 
https://yourfullyqualifieddomaini/proxy for the reverse proxy dashboard (look at comments in traefik.toml and docker-compose.yml to enable)
https://yourfullyqualifieddomain  for the top level landing page
https://yourfullyqualifieddomain/appref   for an individual app (replace appref with the actual app reference.)

After configuration you can start your toolsuite with.... 
${PWD}/bin/up
and you can stop your toolsuite with..
${PWD}/bin/down

you can rerun this setup with...
cd ${DEPLOYDIR}
bash bin/setup

You may want to occasionally rerun the setup to pick up bug fixes and newly released apps for your deployment. This URL
https://www.esrl.noaa.gov/gsd/mats/appProductionStatus/versions shows the latest versions of the apps available for all the deployments.
By comparing those versions to the versions in your running apps you can determine if an update is necessary. You
can tell the version of a running app by looking at the comments in the docker-compose.yml file. This file is recreated when
you run the setup, with the current version for each app.

%END

cat ./README

# test for jq
jq --version
if [[ ! $? -eq 0  ]]; then
    echo -e ${RED} You do not have jq installed. Exiting ${NC}
    exit 1
fi 
#test for docker
docker --version
if [[ ! $? -eq 0  ]]; then
    echo -e ${RED} You do not have docker installed. Exiting ${NC}
    exit 1
fi 
#test for docker-compose
docker-compose --version
if [[ ! $? -eq 0  ]]; then
    echo -e ${RED} You do not have docker-compose installed. Exiting ${NC}
    exit 1
fi 
echo -e ${NC}
domain=$(hostname)

runenv="production"
cp web/custom.html web/index.html
echo -e ${ORNG}
read -p "Is '${domain}' the fully qualified hostname for this deployment? y|n" -n 1 -r
echo -e ${NC}
if [[ $REPLY =~ ^[Yy]$ ]]; then
    echo -e "${GRN}setting up for ${domain}${NC}"
else
    echo -e ${RED}
    read -p "Enter the fully qualified hostname:" domain
    echo -e ${NC}
    echo -e "${GRN}setting up for ${domain}${NC}"
fi

proxyHost=${domain}
read -p "Is '${proxyHost}' the fully qualified proxy hostname for this deployment (if you have no proxy then it should match ${domain})? y|n" -n 1 -r
echo -e ${NC}
if [[ $REPLY =~ ^[Yy]$ ]]; then
    echo -e "${GRN}setting proxyHost to ${domain}${NC}"
else
    echo -e ${RED}
    read -p "Enter the fully qualified proxy hostname:" proxyHost
    echo -e ${NC}
    echo -e "${GRN}setting proxyHost to ${proxyHost}${NC}"
fi

proxyPath=""
read -p "Is there a proxy path for this deployment? e.e '/gsd/mats' y|n" -n 1 -r
echo -e ${NC}
if [[ $REPLY =~ ^[Nn]$ ]]; then
    echo -e "${GRN}Leaving proxy path empty ${NC}"
else
    echo -e ${RED}
    read -p "Enter the proxy path:" proxyPath
    proxyPath=${proxyPath%/}  # remove any trailing slash
    echo -e ${NC}
    echo -e "${GRN}setting proxyPath to ${proxyPath}${NC}"
    echo -e "${GRN} The route to the main page will be https://${proxyHost}${proxyPath}"
fi
#record the environment
echo "{\"environment\":\"${runenv}\",\"proxy_prefix\":\"${proxyPath}\"}" > web/environment.json

htpasswd=$(which htpasswd)
if [[ $? == 0 ]]; then
    adminPhrase=$($htpasswd -nbB admin adminpassword)
fi
unqualifiedHostName=$(echo ${domain} | cut -f1 -d'.')
#create the traefik.toml file....
cat <<- %ENDTRAEFIK > ./traefik.toml
#To set the passwd for the proxy dashboard use
#sudo apt-get install apache2-utils
#htpasswd -nbB admin your_special_password
#replace "admin:$apr1$GZ23gG9v$8slcUumLt29SsZ9MiL.z30" in the "users =" line of the [entryPoints.dashboard.auth.basic]
#entrypoint below. The traefik user challenge will then work for the user admin and your_special_password
logLevel = "INFO"
defaultEntryPoints = ["http", "https"]
[entryPoints]
  [entryPoints.dashboard]
    address = ":8090"
    [entryPoints.dashboard.auth]
      [entryPoints.dashboard.auth.basic]
        users = ["${adminPhrase}"]
  [entryPoints.http]
    address = ":80"
      [entryPoints.http.redirect]
        entryPoint = "https"
  [entryPoints.https]
    address = ":443"
# uncomment the following four lines IF YOUR DOMAIN and PROXY ARE DIFFERENT
# and you want to use both paths i.e. https://mydomain/myproxypath/anapp
# AND https://myproxyhost/myproxypath/anapp
# Or if the domain and the proxy are the same - it takes care of missing trailing slashes.
    [entryPoints.https.redirect]
        regex = "^(https://${domain}${proxyPath})/?$"
        replacement = "\$1/"
        permanent = true
    [entryPoints.https.tls]
#      [[entryPoints.https.tls.certificates]]
#      certFile = "/etc/ssl/certs/${unqualifiedHostName}.crt"
#      keyFile = "/etc/ssl/certs/${unqualifiedHostName}.key"
[api]
  entrypoint="dashboard"
[docker]
domain = "${domain}"
watch = true
network = "web"
exposedbydefualt = false
[traefikLog]
  filePath = "/logs/traefik.log"
[accessLog]
  filePath = "/logs/access.log"
%ENDTRAEFIK

echo -e ${GRN}configuring applist...  ${app}${NC}
echo "[ {\"app\": \"${app}\",\"title\": \"${app}\",\"group\": \"CUSTOM\"}]" > web/applist.json

appref=${app}
if [[ ! -d "./settings/${appref}"  ]]; then
    echo -e "${RED}Creating a ./settings/${appref} directory${NC}"
    mkdir settings/${appref}
fi
if [[ -f  "./settings/${appref}/settings.json" ]]; then
    read -p "Do you want to keep the existing settings for ${appref}? y|n" -n 1 -r
    echo -e ${NC}
    if [[ $REPLY =~ ^[Yy]$ ]]; then
        echo -e "${GRN}skipping setting up for ${appref}${NC}"
        continue
    fi
fi
dbroles=("model_data" "meta_data" "sums_data" "site_data")
# set the settings files

lim=$(( ${#dbroles[@]} - 1 ))
echo -e ${GRN} creating settings file ./settings/${appref}/settings.json with database role ${dbroles[@]}
echo '{"private":{"databases": [ ' > $tmpSettingsFile
# need a databse entry for each database role
wrote=0
for (( ri=0; ri<${#dbroles[@]}; ri++ )); do
    # add a comma if not on the first or last one
    if [[ $ri -lt ${lim} ]] && [[ $wrote -gt 0 ]] && [[ ${#dbroles[@]} -gt 1 ]]; then
        echo "," >> $tmpSettingsFile
    fi
    role=${dbroles[$ri]}
    echo -e ${ORNG}
    read -p "Skip the role ${role} y|n?" -n 1 -r
    echo -e ${NC}
    if [[ $REPLY =~ ^[Yy]$ ]]; then
        wrote=0
        continue
    fi

    if [[ -f "settings/${appref}/settings.json"  ]]; then
        echo -e ${ORNG}loading existing settings${NC}
        xdhost=$(jq -r --arg r "${role}" '.private.databases[] | select(.role==$r).host' < settings/${appref}/settings.json)
        if [[ "X${xdhost}"  != "X" ]]; then
            dhost=${xdhost}
        fi
        xdport=$(jq -r --arg r "${role}" '.private.databases[] | select(.role==$r).port' < settings/${appref}/settings.json)
        if [[ "X${xdport}"  != "X" ]]; then
            dport=${xdport}
        fi
        xduserName=$(jq -r --arg r "${role}" '.private.databases[] | select(.role==$r).user' < settings/${appref}/settings.json)
        if [[ "X${xduserName}"  != "X" ]]; then
            duserName=${xduserName}
        fi
        xdpassword=$(jq -r --arg r "${role}" '.private.databases[] | select(.role==$r).password' < settings/${appref}/settings.json)
        if [[ "X${xdpassword}"  != "X" ]]; then
            dpassword=${xdpassword}
        fi
        xddatabase=$(jq -r --arg r "${role}" '.private.databases[] | select(.role==$r).database' < settings/${appref}/settings.json)
        if [[ "X${xddatabase}"  != "X" ]]; then
            ddatabase=${xddatabase}
        fi
    fi
    good=n
    reset="set"
    while [[ "$good" = "n" ]]; do
        echo -e ${ORNG} Have to ${reset} database server url and database user credentials for ${appref} role ${dbroles[$ri]}
        read -p "What is the host of the database server for ${appref} with role ${role} [$dhost]" host
        host=${host:-$dhost}
        dhost=$host
        read -p "What is the port of the database  server for ${appref} with role ${role} [$dport]" port
        port=${port:-$dport}
        dport=$port
        read -p "What is the database for ${appref} with role ${role} [$ddatabase]" database
        database=${database:-$ddatabase}
        ddatabase=$database
        read -p "What is the username for database ${database} for ${appref} with role ${role} [$duserName]" userName
        userName=${userName:-$duserName}
        duserName=$userName
        cgood="n"
        resetpassword=""
        if [[ "X${dpassword}" != "X" ]]; then
            read -sp "Keep the existing password for ${database} and ${userName} for ${appref} with role ${role} y|n?" -n 1 -r
            echo
         else
            REPLY="n" # there isn't any password - have to ask for it
        fi
        if [[ $REPLY =~ ^[Yy]$ ]]; then
            password=${dpassword}
        else
            while [[ "$cgood" = "n" ]]; do
                echo -e ${ORNG} ${resetpassword}
                read -sp "What is the password for ${database} and ${userName} for ${appref} and role ${role}" password
                echo
                read -sp "Confirm the password for ${database}" cpassword
                echo -e ${NC}
                if [[ "$password" = "$cpassword" ]]; then
                    cgood="y"
                else
                    echo -e "${RED}passwords do not match!!!"${NC}
                    resetpassword="RE-ENTER"
                fi
            done
        fi
        echo -e "${GRN}Setting ${appref} database credentials for ${host}:${port} ${database} with user: ${userName}${NC}"
        echo -e ${ORNG}
        read -p "Good with that? y|n?" -n 1 -r
        echo -e ${NC}
        if [[ $REPLY =~ ^[Yy]$ ]]; then
            good="y"
        else
            echo -e ${RED} redoing ${appref} then... ${NC}
            reset=RESET
        fi
    done

    # build the list of database entries

    database=${database:-$ddatabase}
    ddatabase=$database
    cat <<- %ENDPOOLS >> $tmpSettingsFile
    {
          "role": "${role}",
          "status": "active",
          "host": "${host}",
          "port": "${port}",
          "user": "${userName}",
          "password": "${password}",
          "database": "${database}",
          "connectionLimit": 4
    }
%ENDPOOLS
    wrote=1
done
#finish up the document
group="Ungrouped"
app_order="0"
title="Unknown App"
color="#3366bb"
if [[ "${appref}"  == "met-airquality" ]]; then
    group="METexpress Surface"
    app_order="1"
    title="MET Air Quality"
    color="darkorchid"
fi
if [[ "${appref}"  == "met-anomalycor" ]]; then
    group="METexpress Upper Air"
    app_order="1"
    title="MET Anomaly Correlation"
    color="darkorchid"
fi
if [[ "${appref}"  == "met-cyclone" ]]; then
    group="METexpress Specialized"
    app_order="1"
    title="MET Cyclone"
    color="darkorchid"
fi
if [[ "${appref}"  == "met-ensemble" ]]; then
    group="METexpress Specialized"
    app_order="0"
    title="MET Ensemble"
    color="darkorchid"
fi
if [[ "${appref}"  == "met-object" ]]; then
    group="METexpress Specialized"
    app_order="2"
    title="MET Object"
    color="darkorchid"
fi
if [[ "${appref}"  == "met-precip" ]]; then
    group="METexpress Precipitation"
    app_order="0"
    title="MET Precipitation"
    color="darkorchid"
fi
if [[ "${appref}"  == "met-surface" ]]; then
    group="METexpress Surface"
    app_order="0"
    title="MET Surface"
    color="darkorchid"
fi
if [[ "${appref}"  == "met-upperair" ]]; then
    group="METexpress Upper Air"
    app_order="0"
    title="MET Upper Air"
    color="darkorchid"
fi
echo "], \"PYTHON_PATH\":\"/usr/bin/python3\", \"MAPBOX_KEY\":\"${mapbox_key}\" }, \"public\":{\"run_environment\":\"${depenv}\",\"proxy_prefix_path\":\"${proxyPath}\",\"home\":\"https://${proxyHost}${proxyPath}/\",\"mysql_wait_timeout\":\"900\",\"group\":\"${group}\",\"app_order\":\"${app_order}\",\"app\":\"${appref}\",\"title\":\"${title}\",\"color\":\"${color}\"}}" >> $tmpSettingsFile
jq '.' < $tmpSettingsFile > ./settings/${appref}/settings.json

#set up the dockerfile
echo -e ${ORNG} Setting up a dockercompose file for your apps ${NC}

#NOTE: Spacing is important for compose files!!!! They are yaml!!!!
cat <<- %COMPOSEEND > $tmpDockerFile
version: "3.2"

networks:
 web:
  external: true
 backend:
  external: false

services:

 traefik:
  image: traefik
  deploy:
    restart_policy:
      condition: on-failure
      delay: 5s
      max_attempts: 3
      window: 120s
  networks:
   - web
  ports:
   - "443:443"
  volumes:
   - /var/run/docker.sock:/var/run/docker.sock
   - ${DEPLOYDIR}/traefik.toml:/traefik.toml
   - ${DEPLOYDIR}/logs:/logs
   - /etc/ssl/certs:/etc/ssl/certs
  labels:
   - traefik.frontend.rule=Host:${domain};PathPrefixStrip:${proxyPath}/proxy
   - traefik.port=8090

 mats-http:
  image: halverneus/static-file-server:latest
  deploy:
    restart_policy:
      condition: on-failure
      delay: 5s
      max_attempts: 3
      window: 20s
  volumes:
   - ${DEPLOYDIR}/web:/web
  labels:
   - traefik.backend=mats-http/index.html
   - traefik.frontend.rule=Host:${domain};PathPrefixStrip:${proxyPath}/
   - traefik.docker.network=web
   - traefik.port=8080
  networks:
   - web

 mongo:
  image: mongo
  deploy:
    restart_policy:
      condition: on-failure
      delay: 5s
      max_attempts: 3
      window: 30s
  command: -nojournal
  ports:
   - "27017:27017"
  volumes:
   - ${HOME}/mongodata:/data/db
  networks:
   - backend
   - web

 ${appref}:
  image: ${image}
  deploy:
    restart_policy:
      condition: on-failure
      delay: 5s
      max_attempts: 3
      window: 60s
  environment:
   - DELAY=6
   - ROOT_URL=https://${proxyHost}${proxyPath}/${appref}
  volumes:
   - ${DEPLOYDIR}/settings:/usr/app/settings
  depends_on:
   - mongo
  labels:
   - traefik.backend=${appref}
   - traefik.frontend.rule=Host:${domain};PathPrefix:${proxyPath}/${appref}{id:[0-9]?}
   - traefik.docker.network=web
   - traefik.port=80
  networks:
   - web
   - backend

%COMPOSEEND
mv $tmpDockerFile docker-compose.yml
echo -e ${GRN} "app URL is https://${domain}${proxyPath}/${appref}"
exit 0
