#!/bin/bash

#####################################################################
#
# Copyright (C) Nginx, Inc.
#
# Author:  Rick Nelson, NGINX Inc.
# Version: 0.8.2
# Date:    2024-05-24
#
# This script will install the latest version of NGINX Plus on Ubuntu,
# Debian, RedHat, CentOS, Oracle Linux, Amazon Linux, Suse or FreeBSD.
# It must be run as root.
#
# For trial users, they can enter their token on the command line or
# at a prompt and their cert and key will be downloaded before the
# installation is done. The script can also be used without a token if
# the user installs their cert and key in /etc/ssl/nginx.
#
# Note: wget and bash version 4 are required by this script, so the
# script checks to make sure they are installed. This is mostly an
# issue with FreeBSD and lean installs of Linux.  For Ubuntu and Debian
# lsb_release is also required, so that is checked as well.
#
# This script does the following:
#
# 1. The OS and version will be checked to see if they are supported and
#    have all the prerequisites.
# 2. If the user did not enter a token on the command line, they will be
#    prompted for a token.
# 3. If a token was entered, it will be used to download the cert and key
#    to /etc/ssl/nginx.
# 4. The cert and key directory (/etc/ssl/nginx) is checked to make sure that
#    it contains nginx-repo.crt and nginx-repo.key.
# 5. The expiration date of the certificate will be checked to make
#    sure it hasn't expired. If a token is used, then the certificate
#    should always have a valid date.
# 6. NGINX Plus is installed.
#
# The supported operating systems are:
#    Ubuntu
#    Debian
#    RedHat, CentOS, Oracle Linux
#    Suse
#    FreeBSD
#
# Note: We avoid setting global variables in functions.  For
#       functions that don't print anything to the user we use
#       command subsitition and write the return value to stdout.
#       For functions that do print text to the user we pass in
#       the value to be set and use eval.
#####################################################################

#####################################################################
# Function checkOSPrereqs
#
# Check to make sure that this script is being run using bash version
# 4 and that wget and sudo are installed
#
# Return: 0 for success, 1 for error
#####################################################################
checkOSPrereqs () {

    rc=0

    n=`echo $BASH_VERSION | awk -F. '{print $1}'`
    if [ "$n" -lt 4 ]; then
        echoErr "Error: This script must be run with bash version 4 or newer.";
        rc=1
    fi

    n=`wget -V 2>&1 | grep "GNU Wget"`
    if [ -z "$n" ]; then
        echoErr "Error: wget not found in path.  It must be installed"\
        "to run this script"
        rc=1
    fi

    return $rc
}

#####################################################################
# Function checkLSBRelease
#
# Check to make sure that lsb_release is installed
#
# Return: 0 for success, 1 for error
#####################################################################
XcheckLSBRelease () {

    rc=0

    n=`lsb_release -i 2>&1 | grep "Distributor"`
    if [ -z "$n" ]; then
        echoErr "Error: lsb_release not found in path.  It must be installed"\
        "to run this script"
        rc=1
    fi

    return $rc
}

#####################################################################
# Function getOS
#
# Getting the OS is not the same on all distributions.  First we use
# uname to find out if we are running on Linux or FreeBSD. For all the
# supported versions of Debian and Ubuntu we expect to find the
# /etc/os-release file which has multiple lines with name value pairs
# from which we can get the OS name and version. For RedHat and its
# varients, the os-release file may or may not exist, depending on the
# version.  If it doesn't, then we look for release package and
# get the OS and version from package name. For FreeBSD we use the
# "uname -rs" command.
#
# A string is written to stdout with three values separated by ":":
#    OS
#    OS Name
#    OS Version
#
# If none of these files was found, an empty string is written.
#
# Return: 0 for success, 1 for error
#####################################################################
getOS () {

    local os=""
    local osName=""
    local osVersion=""
    local releaseText=""

    LC_ALL=C

    os=`uname | tr A-Z a-z`

    if [ "$os" != "linux" ] && [ "$os" != "freebsd" ]; then
        echoErr "Error: Operating system is not Linux or FreeBSD"
        echo
        return 1
    fi

    if [ "$os" = "linux" ]; then
        if [ -f $osRelease ]; then
            # The value for the ID and VERSION_ID may or may not be in quotes
            osName=`cat $osRelease | grep "^ID=" | sed s/\"//g | awk -F= '{ print $2 }'`
            osVersion=`cat $osRelease | grep "^VERSION_ID=" | sed s/\"//g | awk -F= '{ print $2 }'`
        else
            # rhel or centos 6.*
            if rpm -q redhat-release-server >/dev/null 2>&1; then
                osName=rhel
                osVersion=`rpm -q redhat-release-server |sed 's/.*-//' | awk -F. '{print $1"."$2;}'`
            elif rpm -q centos-release >/dev/null 2>&1; then
                osName=centos
                osVersion=`rpm -q centos-release | sed 's/centos-release-//' | sed 's/\..*//' | awk -F- '{print $1"."$2;}'`
            else
                echoErr "Error: Unable to determine operating system and version or unsupported OS"
                echo
                return 1
            fi
        fi
    else
        osName=$os
        osVersion=`uname -rs | awk -F '[ -]' '{print $2}'`
        if [ -z $osVersion ]; then
            echoErr "Unable to get FreeBSD version"
            echo
            return 1
        fi
    fi

    # Force osName to lowercase
    osName=`echo $osName | tr A-Z a-z`
    echoDebug "getOS: os=$os osName=$osName osVersion=$osVersion"
    echo "$os:$osName:$osVersion"

    return 0
}

#####################################################################
# Function checkToken
#
# Return: 0 for success, 1 for error
#####################################################################
checkToken() {

    case "$1" in
        *[!a-f0-9]*)
            echoErr "Token $1 is invalid"
            return 1
            ;;
        '')
            echoDebug "checkToken is empty"
            return 0
            ;;
        *)
            if [ ${#1} -ne 32 ]; then
                echoErr "Token $1 is invalid"
                return 1
            fi

            echoDebug "checkToken: Matched"
            return 0
            ;;
    esac
}

#####################################################################
# Function getToken
#
# Prompt the user for the trial token.  The variable to set with the
# token value is passed as an argument.
#####################################################################
getToken () {

    local __token=$1
    local myToken

    while true; do
        echo
        echo "Please enter your trial token.  This will allow the"
        echo "certificate and key to be downloaded that are required"
        echo "to access the NGINX Plus repository."
        echo
        echo "***************************************************************"
        echo "* Note: The certificate and key can only be downloaded once.  *"
        echo "*       This script will download them to /etc/ssl/nginx.     *"
        echo "*       If you need to do additional installations, copy      *"
        echo "*       nginx-repo.crt and nginx-repo.key to the new server.  *"
        echo "***************************************************************"
        echo
        echo "If you do not have the token or it has already been used,"
        echo "leave it blank and put the nginx-repo.crt and nginx-repo.key"
        echo "files into /etc/ssl/nginx."
        echo
        echo -en "Trial token: "
        read myToken
        if [ -z $myToken ] || checkToken $myToken; then
            break;
        fi
    done

    eval $__token="'$myToken'"
    return 0
}

#####################################################################
# Function getCertAndKey
#
# This function will download the nginx-repo.crt and nginx-repo.key
# files to the /etc/ssl/nginx directory.
#
# Note: For some reason FreeBSD is rejecting the cert for cs.nginx.com
#       and requires --no-check-certificate.
#
# Input: The trial token
#
# Return: 0 for success, 1 for error
#####################################################################
getCertAndKey () {

    token=$1

    if [ ! -d "$crtKeyPath" ]; then
        mkdir -p $crtKeyPath
    fi

    certLink="https://cs.nginx.com/otl/$token/cert"
    keyLink="https://cs.nginx.com/otl/$token/private_key"

    wget -nv --no-check-certificate -O $crtKeyPath/nginx-repo.crt $certLink
    # Check that the file exist and is not empty
    if [ -f $crtKeyPath/nginx-repo.crt ] && [ -s $crtKeyPath/nginx-repo.crt ]; then
        wget -nv --no-check-certificate -O $crtKeyPath/nginx-repo.key $keyLink
        if [ -f $crtKeyPath/nginx-repo.key ] && [ -s $crtKeyPath/nginx-repo.key ]; then
            echoDebug "getCertAndKey: Cert and key downloaded"
            return 0
        else
            echoErr -e "\nError downloading nginx-repo.key"
            return 1
        fi
    else
        echoErr -e "\nError downloading nginx-repo.crt"
        return 1
    fi
}

#####################################################################
# Function checkCertKey
#
# Check that the cert and key exist and that the cert hasn't expired
#
# Return: 0 for success, 1 for error
#####################################################################
checkCert() {

    certFile="$1/nginx-repo.crt"
    keyFile="$1/nginx-repo.key"

    if [ ! -f "$certFile" ]; then
        echoErr "Error: $certFile does not exist"
        return 1
    fi
    if [ ! -f "$keyFile" ]; then
        echoErr "Error: $keyFile does not exist"
        return 1
    fi
    openssl x509 -checkend 0 -in $certFile -noout >/dev/null 2>&1
    if [ $? -ne 0 ]; then
        echoErr "Your certificate has expired"
        return 1
    fi
    openssl x509 -checkend 86400 -in $certFile -noout >/dev/null 2>&1
    if [ $? -ne 0 ]; then
        echoErr "Error with certificate end date or current date"
        return 1
    fi
    echoDebug "checkCert: Cert OK"

    return 0
}

#####################################################################
# Function installDebian
#####################################################################
installDebian () {

    echoDebug "Install on Debian"

    wget -q -O /var/tmp/nginx_signing.key https://cs.nginx.com/static/keys/nginx_signing.key && apt-key add /var/tmp/nginx_signing.key
    wget -q -O /etc/apt/apt.conf.d/90pkgs-nginx https://cs.nginx.com/static/files/90pkgs-nginx

    apt-get install -y apt-transport-https lsb-release ca-certificates

    printf "deb https://pkgs.nginx.com/plus/debian `lsb_release -cs` nginx-plus\n" |
           tee /etc/apt/sources.list.d/nginx-plus.list
    apt-get update
    apt-get install -y $NGINXPLUS

    return 0
}

#####################################################################
# Function installUbuntu
#####################################################################
installUbuntu () {

    echoDebug "Install on Ubuntu"

    wget -q -O /var/tmp/nginx_signing.key https://cs.nginx.com/static/keys/nginx_signing.key && apt-key add /var/tmp/nginx_signing.key
    wget -q -O /etc/apt/apt.conf.d/90pkgs-nginx https://cs.nginx.com/static/files/90pkgs-nginx

    apt-get install -y apt-transport-https lsb-release ca-certificates

    printf "deb https://pkgs.nginx.com/plus/ubuntu `lsb_release -cs` nginx-plus\n" |
           tee /etc/apt/sources.list.d/nginx-plus.list

    apt-get update
    apt-get install -y $NGINXPLUS

    return 0
}

#####################################################################
# Function installRedHat
#####################################################################
installRedHat () {

    echoDebug "Install on RedHat/CentOS/Oracle"

    case "$osVersion" in
        10|10.*) release="10"
	      repos="plus-10.repo"
	      ;;
        9|9.*) release="9"
             repos="plus-9.repo"
             ;;
        8|8.*) release="8"
             repos="plus-8.repo"
             ;;
        7|7.*) release="7"
             repos="plus-7.repo"
             ;;
        *)   echo "Unsupported $osName version: $osVersion"
             exit 1
             ;; 
    esac

    for repo in $repos; do
        wget -q -O /etc/yum.repos.d/$repo https://cs.nginx.com/static/files/$repo
    done

    yum install -y $NGINXPLUS

    return 0
}

#####################################################################
# Function installAmazon
#####################################################################
installAmazon () {

    echoDebug "Install on Amazon"

    case "$osVersion" in 
        2023)
         wget -q -O /etc/yum.repos.d/plus-amazonlinux2023.repo https://cs.nginx.com/static/files/plus-amazonlinux2023.repo
         ;;
        2)
         wget -q -O /etc/yum.repos.d/nginx-plus-amazon2.repo https://cs.nginx.com/static/files/nginx-plus-amazon2.repo
         ;;
        *)
         wget -q -O /etc/yum.repos.d/nginx-plus-amazon.repo https://cs.nginx.com/static/files/nginx-plus-amazon.repo
         ;;
    esac

    yum install -y $NGINXPLUS

    return 0
}

#####################################################################
# Function installSuse
#####################################################################
installSuse () {

    echoDebug "Install on Suse"

    local slesVersion=""

    cat /etc/ssl/nginx/nginx-repo.crt /etc/ssl/nginx/nginx-repo.key >\
        /etc/ssl/nginx/nginx-repo-bundle.crt
    slesVersion=`echo $osVersion | awk -F. '{print $1}'`
    zypper addrepo -G -t yum -c "https://pkgs.nginx.com/plus/sles/${slesVersion}?ssl_clientcert=/etc/ssl/nginx/nginx-repo-bundle.crt&ssl_verify=peer" nginx-plus

    zypper install -y $NGINXPLUS

    return 0
}

#####################################################################
# Function installFreeBSD
#####################################################################
installFreeBSD () {

    echoDebug "Install on FreeBSD"

    fetch -o /etc/pkg/nginx-plus.conf http://cs.nginx.com/static/files/nginx-plus.conf

    # It is possible that the script has been run before so, only add
    # the lines if they aren't already there.
    n=`grep "nginx-repo.crt" /usr/local/etc/pkg.conf`
    if [ -z "$n" ]; then
        echo 'PKG_ENV: { SSL_NO_VERIFY_PEER: "1", SSL_CLIENT_CERT_FILE: '\
             '"/etc/ssl/nginx/nginx-repo.crt", SSL_CLIENT_KEY_FILE: '\
             '"/etc/ssl/nginx/nginx-repo.key" }' >> /usr/local/etc/pkg.conf
    fi
    pkg install -y $NGINXPLUS

    return 0
}

#####################################################################
# Function am_i_root
#####################################################################
am_i_root() {

    USERID=`id -u`
    if [ 0 -ne $USERID ]; then
        echoErr "This script requires root privileges to run, exiting."
        exit 1
    fi

    return 0
}

#####################################################################
# Function echoErr
#
# Echo a string to stderr
#####################################################################
echoErr () {

    echo "$*" 1>&2;
}

#####################################################################
# Function echoDebug
#
# Echo a string to stderr if $debug=1
#####################################################################
echoDebug () {

    if [ $debug -eq 1 ]; then
        echo "$@" 1>&2;
    fi
}

#####################################################################
#####################################################################
## Main
#####################################################################
#####################################################################
debug=0 # If set to 1, debug message will be displayed

if ! checkOSPrereqs; then
    exit 1
fi

crtKeyPath="/etc/ssl/nginx"

# The name and location of the files that will be used to get Linux
# release info
osRelease="/etc/os-release"
redhatRelease="/etc/redhat-release"

NGINXPLUS="nginx-plus"

os="" # Will be "linux" or "freebsd"
osName="" # Will be "ubuntu", "debian", "rhel", "almalinux", "rocky",
          # "centos", "suse", "amzn" or "freebsd"
osVersion=""

today=`date +"%Y%m%d"`

token=""

am_i_root

echo
echo "This script will install NGINX Plus"

# Check the OS
osNameVersion=$(getOS)
if [ -z osNameVersion ]; then
    echoErr "Error getting the operating system information"
    exit 1
fi

# Breakout the OS, name and version
os=`echo $osNameVersion | awk -F: '{print $1}'`
osName=`echo $osNameVersion | awk -F: '{print $2}'`
osVersion=`echo $osNameVersion | awk -F: '{print $3}'`

# The token can be passed as an argument, if not, prompt for it
if [ -n "$1" ]; then
    if checkToken "$1"; then
        token=$1
    fi
fi
if [ -z "$token" ]; then
    getToken token
fi

while [ :: ]; do
    echo -en "\nDo you want to install $NGINXPLUS for $osName $osVersion? [y/n]: "
    read response
    case "$response" in
        y|Y)
            break
            ;;
        n|N)
            echo
            exit 1
            ;;
        *)
            echo "Please use 'y' or 'n'"
            ;;
    esac
done

if [ -n "$token" ]; then
    # Download the cert and key
    if ! getCertAndKey $token; then
        exit 1
    fi
fi

if ! checkCert $crtKeyPath; then
    exit 1
fi

# Call the appropriate installation function
case "$osName" in
    debian)
        installDebian
        ;;
    ubuntu)
        installUbuntu
        ;;
    rhel)
        installRedHat
        ;;
    centos)
        installRedHat
        ;;
    ol)
        installRedHat
        ;;
    almalinux)
        installRedHat
        ;;
    rocky)
        installRedHat
        ;;
    amzn)
        installAmazon
        ;;
    sles)
        installSuse
        ;;
    freebsd)
        installFreeBSD
        ;;
    *)
        echo "$osName is not supported"
        exit 1
        ;;
esac
