#!/bin/bash -e

# Copyright (c) 2022 BigBlueButton Inc.
#
# This program is free software; you can redistribute it and/or modify it under the
# terms of the GNU Lesser General Public License as published by the Free Software
# Foundation; either version 3.0 of the License, or (at your option) any later
# version.
#
# BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
# PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License along
# with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.

# BigBlueButton is an open source conferencing system. For more information see
#    https://www.bigbluebutton.org/.
#
# This gl-install.sh script automates many of the installation and configuration
# steps at https://docs.bigbluebutton.org/greenlight/v3/install
#
#
#  Examples
#
#  Install a standaolne Greenlight 3.x.x with a publicly trusted SSL certificate issued by Let's Encrypt using a FQDN of www.example.com
#  and an email address of info@example.com.
#
#    wget -qO- https://raw.githubusercontent.com/bigbluebutton/greenlight/master/gl-install.sh | bash -s -- -s www.example.com -e info@example.com 
#


usage() {
    set +x
    cat 1>&2 <<HERE

Script for installing a Greenlight 3.x standalone server in under 15 minutes. It also supports upgrading an existing installation of Greenlight 3.x on replay.

USAGE:
    wget -qO- https://raw.githubusercontent.com/bigbluebutton/greenlight/master/gl-install.sh | bash -s -- [OPTIONS]

OPTIONS (install Greenlight):

  -s <hostname>          Configure server with <hostname> (Required)

  -e <email>             Email for Let's Encrypt certbot (Required, if -d is omitted)
                          * Cannot be used when -d is used.

  -b <hostname>:<secret> The BigBlueButton server to be used that is accessible on <hostname> with secret <secret> (Optional)
                          * If omitted, defaults to our public BigBlueButton testing server, use it for testing purposes ONLY, DO NOT use for production!

  -d                     Skip SSL certificates generation (Required, if -e is omitted).
                          * Used to provide certificate files skipping the auto generation using Let's encrypt. 
                            Certificate files to be used must be named fullchain.pem and privkey.pem and must be placed in /local/certs/.
                          * Cannot be used when -e is used.

  -k                     Setup Keycloak 20.0 on the system (Optional)
  
  -h                     Print help

VARIABLES (configure Greenlight):
  GL_PATH                Configure Greenlight relative URL root path (Optional)
                          * Use this when deploying Greenlight behind a reverse proxy on a path other than the default '/' e.g. '/gl'.

EXAMPLES:

Sample options for setup a Greenlight 3.x server with a publicly signed (by Let's encrypt) SSL certificate for a FQDN of www.example.com and an email
of info@example.com that uses a BigBlueButton server at bbb.example.com with secret SECRET: 

    -s www.example.com -e info@example.com -b bbb.example.com:SECRET

Sample options for setup a Greenlight 3.x server with pre-owned SSL certificates for a FQDN of www.example.com that uses a BigBlueButton server at bbb.example.com with secret SECRET: 

    -s www.example.com -b bbb.example.com:SECRET -d

SUPPORT:
         Community: https://groups.google.com/g/bigbluebutton-greenlight
         Source: https://github.com/bigbluebutton/greenlight-run
         Docs: https://docs.bigbluebutton.org/greenlight/v3/install

HERE
}

main() {
  export DEBIAN_FRONTEND=noninteractive
  LETS_ENCRYPT_OPTIONS="--webroot --non-interactive"
  SOURCES_FETCHED=false
  GL3_DIR=~/greenlight-v3
  ACCESS_LOG_DEST=/var/log/nginx
  NGINX_FILES_DEST=/etc/greenlight/nginx
  ASSETS_DEST=/var/www/greenlight-default/assets
  EXIT_CODE=0

  # Eager checks and assertions.
  check_root
  check_ubuntu_lts
  need_x64

  while builtin getopts "s:e:b:hdk" opt "${@}"; do

    case $opt in
      h)
        usage
        exit 0
        ;;

      s)
        HOST=$OPTARG
        if [ "$HOST" == "bbb.example.com" ]; then 
          err "You must specify a valid FQDN (not the FQDN given in the docs)."
        fi
        ;;
      e)
        EMAIL=$OPTARG
        if [ "$EMAIL" == "info@example.com" ]; then 
          err "You must specify a valid email address (not the email in the docs)."
        fi
        ;;
      b)
        BIGBLUEBUTTON=$OPTARG
        if [ "$BIGBLUEBUTTON" == "bbb.example.com:SECRET" ]; then 
          err "You must use a valid BigBlueButton server (not the one in the example)."
        fi

        if [[ ! $BIGBLUEBUTTON =~ .+:.+ ]]; then
          err "You must respect the format <hostname>:<secret> when specifying your BigBlueButton server."
        fi

        IFS=: BIGBLUEBUTTON=($BIGBLUEBUTTON) IFS=' ' # Making BIGBLUEBUTTON an array, first element is the BBB hostname and the second is the BBB secret.
        ;;
      d)
        PROVIDED_CERTIFICATE=true
        ;;      
      k)
        INSTALL_KC=true
        ;;      
      :)
        err "Missing option argument for -$OPTARG"
        ;;

      \?)
        err "Invalid option: -$OPTARG"
        ;;
    esac
  done

  GL_DEFAULT_PATH=/
  if [ -n "$GL_PATH"  ] && [ "$GL_PATH" != "$GL_DEFAULT_PATH" ]; then
    if [[ ! $GL_PATH =~ ^/.*[^/]$ ]]; then
      err "\$GL_PATH ENV is set to '$GL_PATH' which is invalid, Greenlight relative URL root path must start but not end with '/'."
    fi
  fi

  check_env # Meeting requirements.

  say "Environment checks passed, installing/upgrading Greenlight!"

  apt-get update
  apt-get -y -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confnew" dist-upgrade
  
  install_ssl
  install_greenlight_v3

  apt-get auto-remove -y
  say "DONE ^^"

  return $EXIT_CODE
}

check_env() {
  # Required ARGS
  if [ -z "$HOST" ]; then
    err "Missing required ARG, You must provide the -s <FQDN>; FQDN must point to this system public IP that Greenlight will be accessible through."
  fi

  if [ -n "$PROVIDED_CERTIFICATE" ] && [ -n "$EMAIL" ]; then
    err "Illegal usage of options, either use -d to provide your already generated certificates OR use -e <EMAIL> to have this script generate one for you."
  fi

  if [ -z "$PROVIDED_CERTIFICATE" ] && [ -z "$EMAIL" ]; then
    err "Missing required ARG, You must provide the -e <EMAIL> to auto generate a certificate OR use -d to include your own files skipping issuing them by the script."
  fi

  local bbb_detected_err="This deployment installs Greenlight without BigBlueButton if planning to install both on the same system then please follow https://github.com/bigbluebutton/bbb-install instead."

  # Detecting BBB on the system
  if [ "${BIGBLUEBUTTON[0]}" == "$HOST" ]; then
    say "Your FQDN match that of the BigBlueButton server to be used, are you willing to install Greenlight with BigBlueButton on this system?"
    err "$bbb_detected_err."
  fi

  if dpkg -l | grep -q bbb; then
    say "BigBlueButton modules has been detected on this system!" 
    err "$bbb_detected_err."
  fi

  # Possible conflicts on setup.
  if [ ! -f /etc/nginx/sites-available/greenlight ]; then
    # Conflict detection of existent nginx on the system not installed by this script (possible collision with other applications).
    if dpkg -s nginx 1> /dev/null 2>&1; then
      say "Nginx is already installed on this system by another mean, this deployment may impact your workload!"
      err "Remove and cleanup nginx configurations on this system OR kindly consider using a clean enviroment before proceeding."
    fi

    # Conflict detection of required ports being already in use.
    if check_ports_listen ':80$|:443$|:5050$|:5151$'; then
      say "Some required ports are already in use by another application!"
      err "Make sure to clear out the required ports (TCP 80, 443, 5050, 5151) if possible OR kindly consider using a clean enviroment before proceeding."
    fi
  fi

  check_host "$HOST"
}

say() {
  echo "gl-install: $1"
}

err() {
  say "$1" >&2
  exit 1
}

warn() {
  say "$1" >&2
  EXIT_CODE=1
}

check_root() {
  if [ $EUID != 0 ]; then err "You must run this command as root."; fi
}

check_ubuntu_lts() {
  lsb_release -i | grep -iq ubuntu || err "You must run this command on Ubuntu server."
  RELEASE=$(lsb_release -r | sed 's/^[^0-9]*//g')
  [ "$RELEASE" == "20.04" ] || [ "$RELEASE" == "22.04" ]  || err "You must run this command on Ubuntu version 20.04 or 22.04 LTS."
}

need_x64() {
  UNAME=`uname -m`
  if [ "$UNAME" != "x86_64" ]; then err "You must run this command on a 64-bit server."; fi
}

wait_443() {
  check_ports_clearing ':443$' && say "Waiting for port 443 to clear "

  while check_ports_clearing ':443$'; do sleep 1; echo -n '.'; done
  echo
}

check_ports_listen() {
  local pattern=${1:-':80$|:443$'}

  ss -lnt | awk '{print $4}' | egrep -q "$pattern"
}

check_ports_clearing() {
  local pattern=${1:-':80$|:443$'}

  ss -ant | grep TIME-WAIT | awk '{print $4}' | egrep -q "$pattern"
}

get_IP() {
  if [ -n "$IP" ]; then return 0; fi

  # Determine local IP
  if [ -e "/sys/class/net/venet0:0" ]; then
    # IP detection for OpenVZ environment
    _dev="venet0:0"
  else
    _dev=$(awk '$2 == 00000000 { print $1 }' /proc/net/route | head -1)
  fi
  _ips=$(LANG=C ip -4 -br address show dev "$_dev" | awk '{ $1=$2=""; print $0 }')
  _ips=${_ips/127.0.0.1\/8/}
  read -r IP _ <<< "$_ips"
  IP=${IP/\/*} # strip subnet provided by ip address
  if [ -z "$IP" ]; then
    read -r IP _ <<< "$(hostname -I)"
  fi


  # Determine external IP 
  if grep -sqi ^ec2 /sys/devices/virtual/dmi/id/product_uuid; then
    # EC2
    local external_ip=$(wget -qO- http://169.254.169.254/latest/meta-data/public-ipv4)
  elif [ -f /var/lib/dhcp/dhclient.eth0.leases ] && grep -q unknown-245 /var/lib/dhcp/dhclient.eth0.leases; then
    # Azure
    local external_ip=$(curl -H Metadata:true "http://169.254.169.254/metadata/instance/network/interface/0/ipv4/ipAddress/0/publicIpAddress?api-version=2017-08-01&format=text")
  elif [ -f /run/scw-metadata.cache ]; then
    # Scaleway
    local external_ip=$(grep "PUBLIC_IP_ADDRESS" /run/scw-metadata.cache | cut -d '=' -f 2)
  elif which dmidecode > /dev/null && dmidecode -s bios-vendor | grep -q Google; then
    # Google Compute Cloud
    local external_ip=$(wget -O - -q "http://metadata/computeMetadata/v1/instance/network-interfaces/0/access-configs/0/external-ip" --header 'Metadata-Flavor: Google')
  elif [ -n "$1" ]; then
    # Try and determine the external IP from the given hostname
    need_pkg dnsutils
    local external_ip=$(dig +short "$1" @resolver1.opendns.com | grep '^[.0-9]*$' | tail -n1)
  fi

  # Check if the external IP reaches the internal IP
  if [ -n "$external_ip" ] && [ "$IP" != "$external_ip" ]; then
    if which nginx; then
      systemctl stop nginx
    fi

    need_pkg netcat-openbsd

    wait_443

    nc -l -p 443 > /dev/null 2>&1 &
    nc_PID=$!
    sleep 1
    
     # Check if we can reach the server through it's external IP address
     if nc -zvw3 "$external_ip" 443  > /dev/null 2>&1; then
       INTERNAL_IP=$IP
       IP=$external_ip
       echo 
       echo "  Detected this server has an internal/external IP address."
       echo 
       echo "      INTERNAL_IP: $INTERNAL_IP"
       echo "    (external) IP: $IP"
       echo 
     fi

    kill $nc_PID  > /dev/null 2>&1;

    if which nginx; then
      systemctl start nginx
    fi
  fi

  if [ -z "$IP" ]; then err "Unable to determine local IP address."; fi
}

need_pkg() {
  check_root
  while fuser /var/lib/dpkg/lock >/dev/null 2>&1; do echo "Sleeping for 1 second because of dpkg lock"; sleep 1; done

  if [ ! "$SOURCES_FETCHED" = true ]; then
    apt-get update
    SOURCES_FETCHED=true
  fi

  if ! dpkg -s ${@:1} >/dev/null 2>&1; then
    LC_CTYPE=C.UTF-8 apt-get install -yq ${@:1}
  fi
  while fuser /var/lib/dpkg/lock >/dev/null 2>&1; do echo "Sleeping for 1 second because of dpkg lock"; sleep 1; done
}

check_host() {
    need_pkg dnsutils apt-transport-https
    DIG_IP=$(dig +short "$1" | grep '^[.0-9]*$' | tail -n1)
    if [ -z "$DIG_IP" ]; then err "Unable to resolve $1 to an IP address using DNS lookup.";  fi
    get_IP "$1"
    if [ "$DIG_IP" != "$IP" ]; then err "DNS lookup for $1 resolved to $DIG_IP but didn't match this system IP $IP."; fi
}

# This function will install the latest official version of greenlight-v3 and set it as the hosting Bigbluebutton default frontend or update greenlight-v3 if installed.
# Greenlight is a simple to use Bigbluebutton room manager that offers a set of features useful to online workloads especially virtual schooling.
# https://docs.bigbluebutton.org/greenlight/v3/install
install_greenlight_v3(){
  check_root
  install_docker

  # Preparing and checking the enviroment.
  say "preparing and checking the enviroment to install/update greelight-v3..."

  if [ ! -d $GL3_DIR ]; then
    mkdir -p $GL3_DIR && say "created $GL3_DIR"
  fi

  local GL_IMG_REPO=bigbluebutton/greenlight:v3

  say "pulling latest $GL_IMG_REPO image..."
  docker pull $GL_IMG_REPO

  if [ ! -s $GL3_DIR/docker-compose.yml ]; then
    docker run --rm --entrypoint sh $GL_IMG_REPO -c 'cat docker-compose.yml' > $GL3_DIR/docker-compose.yml

    if [ ! -s $GL3_DIR/docker-compose.yml ]; then
      err "failed to create docker compose file - is docker running?"
    fi

    say "greenlight-v3 docker compose file was created"
  fi

  # Configuring Greenlight v3.
  say "checking the configuration of greenlight-v3..."

  # Configuring Greenlight v3 docker-compose.yml (if configured no side effect will happen).
  sed -i "s|^\([ \t-]*POSTGRES_PASSWORD\)\(=[ \t]*\)$|\1=$(openssl rand -hex 24)|g" $GL3_DIR/docker-compose.yml # Do not overwrite the value if not empty.

  local PGUSER=postgres # Postgres db user to be used by greenlight-v3.
  local PGTXADDR=postgres:5432 # Postgres DB transport address (pair of (@ip:@port)).
  local RSTXADDR=redis:6379 # Redis DB transport address (pair of (@ip:@port)).
  local PGPASSWORD=$(sed -ne "s/^\([ \t-]*POSTGRES_PASSWORD=\)\(.*\)$/\2/p" $GL3_DIR/docker-compose.yml) # Extract generated Postgres password.

  if [ -z "$PGPASSWORD" ]; then
    err "failed to retrieve greenlight-v3 DB password - retry to resolve."
  fi

  local DATABASE_URL_ROOT="postgres://$PGUSER:$PGPASSWORD@$PGTXADDR"
  local REDIS_URL_ROOT="redis://$RSTXADDR"

  local PGDBNAME=greenlight-v3-production
  local SECRET_KEY_BASE=$(docker run --rm --entrypoint bundle $GL_IMG_REPO exec rails secret)

  if [ -z "$SECRET_KEY_BASE" ]; then
    err "failed to generate greenlight-v3 secret key base - is docker running?"
  fi

  if [ ! -s $GL3_DIR/.env ]; then
    docker run --rm --entrypoint sh $GL_IMG_REPO -c 'cat sample.env' > $GL3_DIR/.env

    if [ ! -s $GL3_DIR/.env ]; then
      err "failed to create greenlight-v3 .env file - is docker running?"
    fi
 
    say "greenlight-v3 .env file was created"
  fi

  # A note for future maintainers:
  #   The following configuration operations were made idempotent, meaning that playing these actions will have an outcome on the system (configure it) only once.
  #   Replaying these steps are a safe and an expected operation, this gurantees the seemless simple installation and upgrade of Greenlight v3.
  #   A simple change can impact that property and therefore render the upgrading functionnality unoperationnal or impact the running system.

  # Configuring Greenlight v3 .env file (if already configured this will only update the BBB endpoint and secret).
  cp -v $GL3_DIR/.env $GL3_DIR/.env.old && say "old .env file can be retrieved at $GL3_DIR/.env.old" #Backup

  if [ -n "$BIGBLUEBUTTON" ]; then
    # BigBlueButton server configuration.
    local BIGBLUEBUTTON_ENDPOINT="https://${BIGBLUEBUTTON[0]}/bigbluebutton/api"
    local BIGBLUEBUTTON_SECRET=${BIGBLUEBUTTON[1]}

    # Re-configure a new BBB server to be used by Greenlight. 
    sed -i "s|^[# \t]*BIGBLUEBUTTON_ENDPOINT=.*|BIGBLUEBUTTON_ENDPOINT=$BIGBLUEBUTTON_ENDPOINT|" $GL3_DIR/.env
    sed -i "s|^[# \t]*BIGBLUEBUTTON_SECRET=.*|BIGBLUEBUTTON_SECRET=$BIGBLUEBUTTON_SECRET|"  $GL3_DIR/.env
  else
    # Demo BigBlueButton server configuration.
    local BIGBLUEBUTTON_ENDPOINT="https://test-install.blindsidenetworks.com/bigbluebutton/api"
    local BIGBLUEBUTTON_SECRET=8cd8ef52e8e101574e400365b55e11a6

    # The demo BBB server should be used when not specifying a dedicated one on installation only (no overwriting).
    sed -i "s|^[# \t]*BIGBLUEBUTTON_ENDPOINT=[ \t]*$|BIGBLUEBUTTON_ENDPOINT=$BIGBLUEBUTTON_ENDPOINT|" $GL3_DIR/.env # Do not overwrite the value if not empty.
    sed -i "s|^[# \t]*BIGBLUEBUTTON_SECRET=[ \t]*$|BIGBLUEBUTTON_SECRET=$BIGBLUEBUTTON_SECRET|"  $GL3_DIR/.env # Do not overwrite the value if not empty.
  fi

  sed -i "s|^[# \t]*SECRET_KEY_BASE=[ \t]*$|SECRET_KEY_BASE=$SECRET_KEY_BASE|" $GL3_DIR/.env # Do not overwrite the value if not empty.
  sed -i "s|^[# \t]*DATABASE_URL=[ \t]*$|DATABASE_URL=$DATABASE_URL_ROOT/$PGDBNAME|" $GL3_DIR/.env # Do not overwrite the value if not empty.
  sed -i "s|^[# \t]*REDIS_URL=[ \t]*$|REDIS_URL=$REDIS_URL_ROOT/|" $GL3_DIR/.env # Do not overwrite the value if not empty.

  # Placing greenlight-v3 nginx file, this will enable greenlight-v3 as your Bigbluebutton frontend (bbb-fe).
  cp -v $NGINX_FILES_DEST/greenlight-v3.nginx $NGINX_FILES_DEST/greenlight-v3.nginx.old && say "old greenlight-v3 nginx config can be retrieved at $NGINX_FILES_DEST/greenlight-v3.nginx.old" #Backup
  docker run --rm --entrypoint sh $GL_IMG_REPO -c 'cat greenlight-v3.nginx' > $NGINX_FILES_DEST/greenlight-v3.nginx && say "added greenlight-v3 nginx file"

  # Adding Keycloak
  if [ -n "$INSTALL_KC" ]; then
      # When attepmting to install/update Keycloak let us attempt to create the database to resolve any issues caused by postgres false negatives.
      docker-compose -f $GL3_DIR/docker-compose.yml up -d postgres && say "started postgres"
      wait_postgres_start
      docker-compose -f $GL3_DIR/docker-compose.yml exec -T postgres psql -U postgres -c 'CREATE DATABASE keycloakdb;'
  fi

  if ! grep -q 'keycloak:' $GL3_DIR/docker-compose.yml; then
    # The following logic is expected to run only once when adding Keycloak.
    # Keycloak isn't installed
    if [ -n "$INSTALL_KC" ]; then
      # Add Keycloak
      say "Adding Keycloak..."

      docker-compose -f $GL3_DIR/docker-compose.yml down
      cp -v $GL3_DIR/docker-compose.yml $GL3_DIR/docker-compose.base.yml # Persist working base compose file for admins as a Backup.

      docker run --rm --entrypoint sh $GL_IMG_REPO -c 'cat docker-compose.kc.yml' >> $GL3_DIR/docker-compose.yml

      if ! grep -q 'keycloak:' $GL3_DIR/docker-compose.yml; then
        err "failed to add Keycloak service to greenlight-v3 compose file - is docker running?"
      fi
      say "added Keycloak to compose file"

      KCPASSWORD=$(openssl rand -hex 12) # Keycloak admin password.
      sed -i "s|^\([ \t-]*KEYCLOAK_ADMIN_PASSWORD\)\(=[ \t]*\)$|\1=$KCPASSWORD|g" $GL3_DIR/docker-compose.yml # Do not overwrite the value if not empty.
      sed -i "s|^\([ \t-]*KC_DB_PASSWORD\)\(=[ \t]*\)$|\1=$PGPASSWORD|g" $GL3_DIR/docker-compose.yml # Do not overwrite the value if not empty.

      # Updating Keycloak nginx file.
      cp -v $NGINX_FILES_DEST/keycloak.nginx $NGINX_FILES_DEST/keycloak.nginx.old && say "old Keycloak nginx config can be retrieved at $NGINX_FILES_DEST/keycloak.nginx.old"
      docker run --rm --entrypoint sh $GL_IMG_REPO -c 'cat keycloak.nginx' > $NGINX_FILES_DEST/keycloak.nginx && say "added Keycloak nginx file"
    fi

  else
    # Update Keycloak nginx file only.
    cp -v $NGINX_FILES_DEST/keycloak.nginx $NGINX_FILES_DEST/keycloak.nginx.old && say "old Keycloak nginx config can be retrieved at $NGINX_FILES_DEST/keycloak.nginx.old"
    docker run --rm --entrypoint sh $GL_IMG_REPO -c 'cat keycloak.nginx' > $NGINX_FILES_DEST/keycloak.nginx && say "added Keycloak nginx file"
  fi

  # Update .env file catching new configurations:
  if ! grep -q 'RELATIVE_URL_ROOT=' $GL3_DIR/.env; then
      cat <<HERE >> $GL3_DIR/.env
#RELATIVE_URL_ROOT=/gl

HERE
  fi

  if [ -n "$GL_PATH" ]; then
    sed -i "s|^[# \t]*RELATIVE_URL_ROOT=.*|RELATIVE_URL_ROOT=$GL_PATH|" $GL3_DIR/.env
  fi

  local GL_RELATIVE_URL_ROOT=$(sed -ne "s/^\([ \t]*RELATIVE_URL_ROOT=\)\(.*\)$/\2/p" $GL3_DIR/.env) # Extract relative URL root path.
  say "Deploying Greenlight on the '${GL_RELATIVE_URL_ROOT:-$GL_DEFAULT_PATH}' path..."

  if [ -n "$GL_RELATIVE_URL_ROOT" ] && [ "$GL_RELATIVE_URL_ROOT" != "$GL_DEFAULT_PATH" ]; then
    sed -i "s|^\([ \t]*location\)[ \t]*\(.*/cable\)[ \t]*\({\)$|\1 $GL_RELATIVE_URL_ROOT/cable \3|" $NGINX_FILES_DEST/greenlight-v3.nginx
    sed -i "s|^\([ \t]*location\)[ \t]*\(@bbb-fe\)[ \t]*\({\)$|\1 $GL_RELATIVE_URL_ROOT \3|" $NGINX_FILES_DEST/greenlight-v3.nginx
  fi

  nginx -qt || err 'greenlight-v3 failed to install/update due to nginx tests failing to pass - if using the official image then please contact the maintainers.'
  nginx -qs reload && say 'greenlight-v3 was successfully configured'

  # Eager pulling images.
  say "pulling latest greenlight-v3 services images..."
  docker-compose -f $GL3_DIR/docker-compose.yml pull

  if check_container_running greenlight-v3; then
    # Restarting Greenlight-v3 services after updates.
    say "greenlight-v3 is updating..."
    say "shutting down greenlight-v3..."
    docker-compose -f $GL3_DIR/docker-compose.yml down
  fi

  say "starting greenlight-v3..."
  docker-compose -f $GL3_DIR/docker-compose.yml up -d
  sleep 5
  say "greenlight-v3 is now installed and accessible on: https://$HOST${GL_RELATIVE_URL_ROOT:-$GL_DEFAULT_PATH}"
  say "To create Greenlight administrator account, see: https://docs.bigbluebutton.org/greenlight/v3/install#creating-an-admin-account"


  if grep -q 'keycloak:' $GL3_DIR/docker-compose.yml; then
    say "Keycloak is installed, up to date and accessible for configuration on: https://$HOST/keycloak/"
    if [ -n "$KCPASSWORD" ];then
      say "Use the following credentials when accessing the admin console:"
      say "   admin"
      say "   $KCPASSWORD"
    fi

    say "To complete the configuration of Keycloak, see: https://docs.bigbluebutton.org/greenlight/v3/external-authentication#configuring-keycloak"
  fi

  return 0;
}

wait_postgres_start() {
  say "Waiting for the Postgres DB to start..."
  docker-compose -f $GL3_DIR/docker-compose.yml up -d postgres || err "failed to start Postgres service - retry to resolve"

  local tries=0
  while ! docker-compose -f $GL3_DIR/docker-compose.yml exec -T postgres pg_isready 2> /dev/null 1>&2; do
    echo -n .
    sleep 3
    if (( ++tries == 3 )); then
      err "failed to start Postgres due to reaching waiting timeout - retry to resolve" 
    fi
  done

  say "Postgres is ready!"

  return 0;
}

install_ssl() {
  # Assertions for fresh installations
  if [ ! -f /etc/nginx/sites-available/greenlight ]; then
    if [ -d "/etc/letsencrypt/live/$HOST" ]; then
      err "Unable to manage certificates for $HOST, /etc/letsencrypt/live/$HOST/ already exists."
    fi
  fi

  if [ -n "$PROVIDED_CERTIFICATE" ]; then
    if [ ! -f /local/certs/fullchain.pem ] || [ ! -f /local/certs/privkey.pem ]; then
      err "Unable to find your provided certificate files in /local/certs, Have you placed the full chain and private key for your certificate as expected?"
    fi

    # Detecting generated certs and possible conflicts.
    if [ -f /etc/letsencrypt/live/$HOST/fullchain.pem ]; then
      if [ ! "$(readlink -e /etc/letsencrypt/live/$HOST/fullchain.pem)" == /local/certs/fullchain.pem ]; then
        err "fullchain.pem was probably generated and not provided by this script, exiting to avoid conflict."
      fi
    fi

    if [ -f /etc/letsencrypt/live/$HOST/privkey.pem ]; then
      if [ ! "$(readlink -e /etc/letsencrypt/live/$HOST/privkey.pem)" == /local/certs/privkey.pem ]; then
        err "privkey.pem was probably generated and not provided by this script, exiting to avoid conflict."
      fi
    fi
  else
    # Detecting provided certs and possible conflicts.
    if [ -f /etc/letsencrypt/live/$HOST/fullchain.pem ]; then
      if [[ ! "$(readlink -e /etc/letsencrypt/live/$HOST/fullchain.pem)" =~ ^/etc/letsencrypt/archive/$HOST.*/fullchain.*\.pem$ ]]; then
        err "fullchain.pem was probably provided and not generated by this script, exiting to avoid conflict."
      fi
      local GENERATED_CERTS_EXIST=true
    fi

    if [ -f /etc/letsencrypt/live/$HOST/privkey.pem ]; then
      if [[ ! "$(readlink -e /etc/letsencrypt/live/$HOST/privkey.pem)" =~ ^/etc/letsencrypt/archive/$HOST.*/privkey.*\.pem$ ]]; then
        err "privkey.pem was probably provided and not generated by this script, exiting to avoid conflict."
      fi
    fi

    need_pkg certbot
  fi

  need_pkg nginx
  mkdir -p /etc/nginx/ssl $ACCESS_LOG_DEST $NGINX_FILES_DEST $ASSETS_DEST

  cp -v /etc/nginx/sites-available/greenlight /etc/nginx/sites-available/greenlight.old # Preserve older config for admins.

  # HTTP only
  # Updating HTTP config.
  cat <<HERE > /etc/nginx/sites-available/greenlight
server_tokens off;
server {
  listen 80;
  listen [::]:80;
  server_name $HOST;

  access_log  $ACCESS_LOG_DEST/greenlight.access.log;

  # Greenlight landing page.
  location / {
    root   $ASSETS_DEST;
    try_files \$uri @bbb-fe;
  }

  include $NGINX_FILES_DEST/*.nginx;
}

HERE

  if [ ! -f /etc/nginx/sites-enabled/greenlight ]; then
    ln -sf /etc/nginx/sites-available/greenlight /etc/nginx/sites-enabled/greenlight # Activate greenlight nginx config.
  fi

  if [ -f /etc/nginx/sites-enabled/default ]; then
    rm -v /etc/nginx/sites-enabled/default # Remove nginx default config.
  fi

  # Validating the config.
  nginx -qt || warn "Unable to configure nginx - if following the official guides then please contact the maintainers."
  systemctl restart nginx

# Enabling HTTPS.
  cp -v /etc/nginx/sites-available/greenlight /etc/nginx/sites-available/greenlight.http # Preserve valid HTTP config for admins.

  if [ -n "$PROVIDED_CERTIFICATE" ]; then
      say "Providing SSL certificates for $HOST..."
      mkdir -p "/etc/letsencrypt/live/$HOST" && say "Created $HOST live directory"
      ln -sf /local/certs/fullchain.pem "/etc/letsencrypt/live/$HOST/fullchain.pem" && say "fullchain.pem found and placed"
      ln -sf /local/certs/privkey.pem "/etc/letsencrypt/live/$HOST/privkey.pem" && say "privkey.pem found and placed"
  else
    # Auto generate a standalone SSL x509 certificate publicly signed by Let's encrypt for this domain $HOST.
    if [ -n "$GENERATED_CERTS_EXIST" ]; then
      say "Checking certificates of $HOST for renewal..."
      if certbot --email "$EMAIL" --agree-tos --rsa-key-size 4096 -w $ASSETS_DEST \
            -d "$HOST" --deploy-hook "systemctl reload nginx" $LETS_ENCRYPT_OPTIONS certonly; then
        say "Renewal checks passed!"
      else
        warn "Something went wrong when attempting to renew certificates!"
      fi
    else
      say "Generating certificates for $HOST..."
      say "Rehearsal phase..."
      if certbot --staging --email "$EMAIL" --agree-tos --rsa-key-size 4096 -w $ASSETS_DEST \
          -d "$HOST" --deploy-hook "systemctl reload nginx" $LETS_ENCRYPT_OPTIONS certonly; then
        say "Generating SSL certificates for $HOST..."
        say "Rehearsal passed, ready to issue production certificates!"
        say "Issuing production certificates..."
        if certbot --force-renewal --email "$EMAIL" --agree-tos --rsa-key-size 4096 -w $ASSETS_DEST \
            -d "$HOST" --deploy-hook "systemctl reload nginx" $LETS_ENCRYPT_OPTIONS certonly; then
          say "Production SSL certificates has been generated!"
        else
          warn "Something went wrong when generating production certificates"
        fi
      else
        warn "Unable to pass the rehearsal phase, avoided generating production certificates to keep rates under limit."
      fi
    fi
  fi

  say "Generating DH key exchange parameters..."
  cp -v /etc/nginx/ssl/dhp-4096.pem /etc/nginx/ssl/dhp-4096.pem.old

  # For security reasons upon upgrade, the Diffie Hellman key exchange parameters will rotate.
  if ! openssl dhparam -dsaparam  -out /etc/nginx/ssl/dhp-4096.pem 4096; then
    warn "Unable to generate DH key exchange parameters - rolling back..."
    mv -v /etc/nginx/ssl/dhp-4096.pem.old /etc/nginx/ssl/dhp-4096.pem || warn "Unable to generate new DH key exchage parameters nor to recover."
  else
    say "DH key exchange parameters was generated!"
  fi

  say "Configuring nginx with SSL enabled..."

  # Updating HTTPS config.
  cat <<HERE > /etc/nginx/sites-available/greenlight
server_tokens off;

server {
  listen 80;
  listen [::]:80;
  server_name $HOST;
  
  return 301 https://\$server_name\$request_uri; #redirect HTTP to HTTPS

}

server {
  listen 443 ssl http2;
  listen [::]:443 ssl http2;
  server_name $HOST;

  ssl_certificate /etc/letsencrypt/live/$HOST/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/$HOST/privkey.pem;
  ssl_session_cache shared:SSL:10m;
  ssl_session_timeout 10m;
  ssl_protocols TLSv1.2 TLSv1.3;
  ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
  ssl_dhparam /etc/nginx/ssl/dhp-4096.pem;
    
  # HSTS (comment out to enable)
  #add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

  access_log  $ACCESS_LOG_DEST/greenlight.access.log;

  # Greenlight landing page.
  location / {
    root   $ASSETS_DEST;
    try_files \$uri @bbb-fe;
  }

  include $NGINX_FILES_DEST/*.nginx;
}

HERE

  # Validating new config
  if ! nginx -qt; then
    # Rollback logic
    warn "Something went wrong configuring nginx - attempting to recover..."

    mv -v /etc/nginx/sites-available/greenlight /etc/nginx/sites-available/greenlight.https # Preserve used HTTPS config for admins.

    if mv -v /etc/nginx/sites-available/greenlight.old /etc/nginx/sites-available/greenlight; then
      warn "Fallen back to previous configuration!"
    else
      cp -v /etc/nginx/sites-available/greenlight.http /etc/nginx/sites-available/greenlight # Preserve used HTTP config for admins while falling back to HTTP. 
      warn "No previous configuration was found - fallen back to http configuration!"
    fi
    
    systemctl restart nginx

    warn "Unable to configure nginx with certificates, retry to resolve."

    return 1
  fi

  say "Nginx was configured successuflly with SSL enabled!"
  systemctl restart nginx && say "Nginx is UP!"

  return 0

}

# Given a container name as $1, this function will check if there's a match for that name in the list of running docker containers on the system.
# The result will be binded to $?.
check_container_running() {
  docker ps | grep -q "$1" || return 1;

  return 0;
}

install_docker() {
  apt-get remove --purge -y docker docker-engine docker.io containerd runc
  need_pkg ca-certificates curl gnupg lsb-release

  # Install Docker
  if ! apt-key list | grep -iq docker; then
    curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor --yes -o /etc/apt/trusted.gpg.d/docker.gpg || err "Something went wrong adding docker gpg key - exiting"
  fi

  if ! dpkg -l | grep -iq docker-ce; then
    echo \
      "deb [ arch=amd64 ] https://download.docker.com/linux/ubuntu \
      $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null

    chmod a+r /etc/apt/trusted.gpg.d/docker.gpg

    apt-get update
    need_pkg docker-ce docker-ce-cli containerd.io docker-compose-plugin
  fi

  if ! which docker; then err "Docker did not install"; fi

  # Purge older docker compose if exists.
  # DEPRECATED
  if dpkg -l | grep -q docker-compose; then
    apt-get purge -y docker-compose
  fi

  if [ ! -x /usr/local/bin/docker-compose ]; then
    curl -L "https://github.com/docker/compose/releases/download/1.24.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
    chmod +x /usr/local/bin/docker-compose
  fi

  if ! docker version > /dev/null ; then
    warn "Docker is failing, restarting it..."
    systemctl restart docker.socket docker.service
    sleep 5

    docker version > /dev/null || err "docker is failing to restart, something is wrong retry to resolve - exiting"
  fi

  say "docker is running!"
  return 0;
}

main "$@" || exit 1
