#!/bin/sh

#
# This script is meant for quick & easy install via:
#   'curl -sSL https://get.replicated.com/ | sudo sh'
# or:
#   'wget -qO- https://get.replicated.com/ | sudo sh'
#

SCRIPT=$(cat <<"%INSTALL_SCRIPT_END%"

set -e

AIRGAP=0
PROXY_ADDRESS=(%proxy_address%)
PRIVATE_ADDRESS=(%private_address%)
SKIP_DOCKER_INSTALL=(%skip_docker_install%)
READ_TIMEOUT="(%read_timeout%)"
PINNED_DOCKER_VERSION="1.12.3"
MIN_DOCKER_VERSION="1.7.1"


#######################################
#
# common.sh
#
#######################################

GREEN='\033[0;32m'
BLUE='\033[0;94m'
LIGHT_BLUE='\033[0;34m'
YELLOW='\033[0;33m'
RED='\033[0;31m'
NC='\033[0m' # No Color

#######################################
# Check if command exists.
# Globals:
#   None
# Arguments:
#   None
# Returns:
#   0 if command exists
#######################################
commandExists() {
    command -v "$@" > /dev/null 2>&1
}

#######################################
# Check if replicated 1.2 is installed
# Globals:
#   None
# Arguments:
#   None
# Returns:
#   0 if replicated 1.2 is installed
#######################################
replicated12Installed() {
    commandExists replicated && replicated --version | grep -q "Replicated version 1\.2"
}

#######################################
# Check if replicated 2.0 is installed
# Globals:
#   None
# Arguments:
#   None
# Returns:
#   0 if replicated 2.0 is installed
#######################################
replicated2Installed() {
    commandExists /usr/local/bin/replicatedctl && /usr/local/bin/replicatedctl version >/dev/null 2>&1
}

#######################################
# Returns replicated 2.0 version
# Globals:
#   None
# Arguments:
#   None
# Returns:
#   INSTALLED_REPLICATED_VERSION
#######################################
replicated2Version() {
    if replicated2Installed; then
        INSTALLED_REPLICATED_VERSION="$(/usr/local/bin/replicatedctl version --quiet)"
    else
        INSTALLED_REPLICATED_VERSION=""
    fi
}

#######################################
# Returns 0 if replicated will downgrade
# Globals:
#   None
# Arguments:
#   Next replicated version
# Returns:
#   0 if replicated version is less than current
#   1 if replicated version is greater than or equal to current
#######################################
isReplicatedDowngrade() {
    if ! replicated2Installed; then
        return 1
    fi

    replicated2Version
    semverCompare "$1" "$INSTALLED_REPLICATED_VERSION"
    if [ "$SEMVER_COMPARE_RESULT" -lt "0" ]; then
        return 0
    fi
    return 1
}

#######################################
# Gets curl or wget depending if cmd exits.
# Globals:
#   PROXY_ADDRESS
# Arguments:
#   None
# Returns:
#   URLGET_CMD
#######################################
URLGET_CMD=
getUrlCmd() {
    if commandExists "curl"; then
        URLGET_CMD="curl -sSL"
        if [ -n "$PROXY_ADDRESS" ]; then
            URLGET_CMD=$URLGET_CMD" -x $PROXY_ADDRESS"
        fi
    else
        URLGET_CMD="wget -qO-"
    fi
}

#######################################
# Generates a 32 char unique id.
# Globals:
#   None
# Arguments:
#   None
# Returns:
#   GUID_RESULT
#######################################
getGuid() {
    GUID_RESULT="$(head -c 128 /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1)"
}

#######################################
# performs in-place sed substitution with escapting of inputs (http://stackoverflow.com/a/10467453/5344799)
# Globals:
#   None
# Arguments:
#   None
# Returns:
#   None
#######################################
safesed() {
    sed -i "s/$(echo $1 | sed -e 's/\([[\/.*]\|\]\)/\\&/g')/$(echo $2 | sed -e 's/[\/&]/\\&/g')/g" $3
}

#######################################
# Parses a semantic version string
# Globals:
#   None
# Arguments:
#   Version
# Returns:
#   major, minor, patch
#######################################
semverParse() {
    major="${1%%.*}"
    minor="${1#$major.}"
    minor="${minor%%.*}"
    patch="${1#$major.$minor.}"
    patch="${patch%%[-.]*}"
}

#######################################
# Compare two semvers.
# Returns -1 if A lt B, 0 if eq, 1 A gt B.
# Globals:
#   None
# Arguments:
#   Sem Version A
#   Sem Version B
# Returns:
#   SEMVER_COMPARE_RESULT
#######################################
SEMVER_COMPARE_RESULT=
semverCompare() {
    semverParse "$1"
    _a_major="${major:-0}"
    _a_minor="${minor:-0}"
    _a_patch="${patch:-0}"
    semverParse "$2"
    _b_major="${major:-0}"
    _b_minor="${minor:-0}"
    _b_patch="${patch:-0}"
    if [ "$_a_major" -lt "$_b_major" ]; then
        SEMVER_COMPARE_RESULT=-1
        return
    fi
    if [ "$_a_major" -gt "$_b_major" ]; then
        SEMVER_COMPARE_RESULT=1
        return
    fi
    if [ "$_a_minor" -lt "$_b_minor" ]; then
        SEMVER_COMPARE_RESULT=-1
        return
    fi
    if [ "$_a_minor" -gt "$_b_minor" ]; then
        SEMVER_COMPARE_RESULT=1
        return
    fi
    if [ "$_a_patch" -lt "$_b_patch" ]; then
        SEMVER_COMPARE_RESULT=-1
        return
    fi
    if [ "$_a_patch" -gt "$_b_patch" ]; then
        SEMVER_COMPARE_RESULT=1
        return
    fi
    SEMVER_COMPARE_RESULT=0
}

#######################################
# Inserts a parameter into a json file. If the file does not exist, creates it. If the parameter is already set, replaces it.
# Globals:
#   None
# Arguments:
#   path, parameter name, value
# Returns:
#   None
#######################################
insertOrReplaceJsonParam() {
    if ! [ -f "$1" ]; then
        # If settings file does not exist
        mkdir -p "$(dirname "$1")"
        echo "{\"$2\": \"$3\"}" > "$1"
    else
        # Settings file exists
        if grep -q -E "\"$2\" *: *\"[^\"]*\"" "$1"; then
            # If settings file contains named setting, replace it
            sed -i -e "s/\"$2\" *: *\"[^\"]*\"/\"$2\": \"$3\"/g" "$1"
        else
            # Insert into settings file (with proper commas)
            if [ $(wc -c <"$1") -ge 5 ]; then
                # File long enough to actually have an entry, insert "name": "value",\n after first {
                _commonJsonReplaceTmp="$(awk "NR==1,/^{/{sub(/^{/, \"{\\\"$2\\\": \\\"$3\\\", \")} 1" "$1")"
                echo "$_commonJsonReplaceTmp" > "$1"
            else
                # file not long enough to actually have contents, replace wholesale
                echo "{\"$2\": \"$3\"}" > "$1"
            fi
        fi
    fi
}

######################################
# Inserts a string array of length 1 into a json file. Fails if key is found in file.
# Globals:
#   None
# Arguments:
#   path, key, value[0]
# Returns:
#   1 if there are errors
######################################
insertJSONArray() {
	if ! [ -f "$1" ] || [ $(wc -c <"$1") -lt 5 ]; then
        mkdir -p "$(dirname "$1")"
		cat > $1 <<EOF
{
  "$2": ["$3"]
}
EOF
		return 0
	fi

	if grep -q "$2" "$1"; then
		return 1
	fi

	_commonJsonReplaceTmp="$(awk "NR==1,/^{/{sub(/^{/, \"{\\\"$2\\\": [\\\"$3\\\"], \")} 1" "$1")"
	echo "$_commonJsonReplaceTmp" > "$1"
	return 0
}

#######################################
# Splits an address in the format "host:port".
# Globals:
#   None
# Arguments:
#   address
# Returns:
#   HOST
#   PORT
#######################################
splitHostPort() {
    oIFS="$IFS"; IFS=":" read -r HOST PORT <<< "$1"; IFS="$oIFS"
}

#######################################
# Checks if Docker is installed
# Globals:
#   None
# Arguments:
#   None
# Returns:
#   0 if Docker is installed
#######################################
isDockerInstalled() {
    commandExists "docker" && ps aux | grep -q '[d]ockerd'
}

#######################################
# Creates and sets REPLICATED_TEMP_DIR variable if not set.
# Globals:
#   None
# Arguments:
#   adNonedress
# Returns:
#   REPLICATED_TEMP_DIR
#######################################
maybeCreateTempDir() {
    if [ -z "$REPLICATED_TEMP_DIR" ]; then
        REPLICATED_TEMP_DIR="$(mktemp -d --suffix=replicated)"
    fi
}

#######################################
#
# prompt.sh
#
#######################################

PROMPT_RESULT=

if [ -z "$READ_TIMEOUT" ]; then
    READ_TIMEOUT="-t 20"
fi


#######################################
# Prompts the user for input.
# Globals:
#   READ_TIMEOUT, FAST_TIMEOUTS
# Arguments:
#   Read timeout, formatted "-t int"
# Returns:
#   PROMPT_RESULT
#######################################
promptTimeout() {
    set +e
    if [ -z "$FAST_TIMEOUTS" ]; then
        read ${1:-$READ_TIMEOUT} PROMPT_RESULT < /dev/tty
    else
        read ${READ_TIMEOUT} PROMPT_RESULT < /dev/tty
    fi
    set -e
}

#######################################
# Confirmation prompt default yes.
# Globals:
#   READ_TIMEOUT, FAST_TIMEOUTS
# Arguments:
#   Read timeout, formatted "-t int"
# Returns:
#   None
#######################################
confirmY() {
    printf "(Y/n) "
    promptTimeout "$@"
    if [ "$PROMPT_RESULT" = "n" ] || [ "$PROMPT_RESULT" = "N" ]; then
        return 1
    fi
    return 0
}

#######################################
# Confirmation prompt default no.
# Globals:
#   READ_TIMEOUT, FAST_TIMEOUTS
# Arguments:
#   Read timeout, formatted "-t int"
# Returns:
#   None
#######################################
confirmN() {
    printf "(y/N) "
    promptTimeout "$@"
    if [ "$PROMPT_RESULT" = "y" ] || [ "$PROMPT_RESULT" = "Y" ]; then
        return 0
    fi
    return 1
}


#######################################
# Prompts the user for input.
# Globals:
#   READ_TIMEOUT
# Arguments:
#   None
# Returns:
#   PROMPT_RESULT
#######################################
prompt() {
    set +e
    read PROMPT_RESULT < /dev/tty
    set -e
}

#######################################
#
# system.sh
#
#######################################

#######################################
# Requires a 64 bit platform or exits with an error.
# Globals:
#   None
# Arguments:
#   None
# Returns:
#   None
#######################################
require64Bit() {
    case "$(uname -m)" in
        *64)
            ;;
        *)
            echo >&2 'Error: you are not using a 64bit platform.'
            echo >&2 'This installer currently only supports 64bit platforms.'
            exit 1
            ;;
    esac
}

#######################################
# Detects the Linux kernel version.
# Globals:
#   None
# Arguments:
#   None
# Returns:
#   KERNEL_MAJOR
#   KERNEL_MINOR
#   KERNEL_PATCH
#######################################
KERNEL_MAJOR=
KERNEL_MINOR=
KERNEL_PATCH=
getKernelVersion() {
    semverParse "$(uname -r)"
    KERNEL_MAJOR=$major
    KERNEL_MINOR=$minor
    KERNEL_PATCH=$patch
}

#######################################
# Requires that the script be run with the root user.
# Globals:
#   None
# Arguments:
#   None
# Returns:
#   USER
#######################################
USER=
requireRootUser() {
    USER="$(id -un 2>/dev/null || true)"
    if [ "$USER" != "root" ]; then
        echo >&2 "Error: This script requires admin privileges. Please re-run it as root."
        exit 1
    fi
}

#######################################
# Detects the linux distribution.
# Upon failure exits with an error.
# Globals:
#   None
# Arguments:
#   None
# Returns:
#   LSB_DIST
#   DIST_VERSION - should be MAJOR.MINOR e.g. 16.04 or 7.4
#   DIST_VERSION_MAJOR
#######################################
LSB_DIST=
DIST_VERSION=
DIST_VERSION_MAJOR=
detectLsbDist() {
    _dist=
    _error_msg="We have checked /etc/os-release and /etc/centos-release files."
    if [ -f /etc/centos-release ] && [ -r /etc/centos-release ]; then
        # CentOS 6 example: CentOS release 6.9 (Final)
        # CentOS 7 example: CentOS Linux release 7.5.1804 (Core)
        # CentOS 9 example: CentOS Stream release 9
        _dist="$(cat /etc/centos-release | cut -d" " -f1)"
        _version="$(cat /etc/centos-release | sed 's/Linux //' | sed 's/Stream //' | cut -d" " -f3 | cut -d "." -f1-2)"
    elif [ -f /etc/os-release ] && [ -r /etc/os-release ]; then
        _dist="$(. /etc/os-release && echo "$ID")"
        _version="$(. /etc/os-release && echo "$VERSION_ID")"
    elif [ -f /etc/redhat-release ] && [ -r /etc/redhat-release ]; then
        # this is for RHEL6
        _dist="rhel"
        _major_version=$(cat /etc/redhat-release | cut -d" " -f7 | cut -d "." -f1)
        _minor_version=$(cat /etc/redhat-release | cut -d" " -f7 | cut -d "." -f2)
        _version=$_major_version
    elif [ -f /etc/system-release ] && [ -r /etc/system-release ]; then
        if grep --quiet "Amazon Linux" /etc/system-release; then
            # Special case for Amazon 2014.03
            _dist="amzn"
            _version=`awk '/Amazon Linux/{print $NF}' /etc/system-release`
        fi
    else
        _error_msg="$_error_msg\nDistribution cannot be determined because neither of these files exist."
    fi

    if [ -n "$_dist" ]; then
        _error_msg="$_error_msg\nDetected distribution is ${_dist}."
        _dist="$(echo "$_dist" | tr '[:upper:]' '[:lower:]')"
        case "$_dist" in
            ubuntu)
                _error_msg="$_error_msg\nHowever detected version $_version is less than 12."
                oIFS="$IFS"; IFS=.; set -- $_version; IFS="$oIFS";
                [ $1 -ge 12 ] && LSB_DIST=$_dist && DIST_VERSION=$_version && DIST_VERSION_MAJOR=$1
                ;;
            debian)
                _error_msg="$_error_msg\nHowever detected version $_version is less than 7."
                oIFS="$IFS"; IFS=.; set -- $_version; IFS="$oIFS";
                [ $1 -ge 7 ] && LSB_DIST=$_dist && DIST_VERSION=$_version && DIST_VERSION_MAJOR=$1
                ;;
            fedora)
                _error_msg="$_error_msg\nHowever detected version $_version is less than 21."
                oIFS="$IFS"; IFS=.; set -- $_version; IFS="$oIFS";
                [ $1 -ge 21 ] && LSB_DIST=$_dist && DIST_VERSION=$_version && DIST_VERSION_MAJOR=$1
                ;;
            rhel)
                _error_msg="$_error_msg\nHowever detected version $_version is less than 6."
                oIFS="$IFS"; IFS=.; set -- $_version; IFS="$oIFS";
                [ $1 -ge 6 ] && LSB_DIST=$_dist && DIST_VERSION=$_version && DIST_VERSION_MAJOR=$1
                ;;
            centos)
                _error_msg="$_error_msg\nHowever detected version $_version is less than 6."
                oIFS="$IFS"; IFS=.; set -- $_version; IFS="$oIFS";
                [ $1 -ge 6 ] && LSB_DIST=$_dist && DIST_VERSION=$_version && DIST_VERSION_MAJOR=$1
                ;;
            amzn)
                _error_msg="$_error_msg\nHowever detected version $_version is not one of\n    2, 2.0, 2023, 2018.03, 2017.09, 2017.03, 2016.09, 2016.03, 2015.09, 2015.03, 2014.09, 2014.03."
                [ "$_version" = "2" ] || [ "$_version" = "2.0" ] || \
                [ "$_version" = "2023" ] || \
                [ "$_version" = "2018.03" ] || \
                [ "$_version" = "2017.03" ] || [ "$_version" = "2017.09" ] || \
                [ "$_version" = "2016.03" ] || [ "$_version" = "2016.09" ] || \
                [ "$_version" = "2015.03" ] || [ "$_version" = "2015.09" ] || \
                [ "$_version" = "2014.03" ] || [ "$_version" = "2014.09" ] && \
                LSB_DIST=$_dist && DIST_VERSION=$_version && DIST_VERSION_MAJOR=$_version
                ;;
            sles)
                _error_msg="$_error_msg\nHowever detected version $_version is less than 12."
                oIFS="$IFS"; IFS=.; set -- $_version; IFS="$oIFS";
                [ $1 -ge 12 ] && LSB_DIST=$_dist && DIST_VERSION=$_version && DIST_VERSION_MAJOR=$1
                ;;
            ol)
                _error_msg="$_error_msg\nHowever detected version $_version is less than 6."
                oIFS="$IFS"; IFS=.; set -- $_version; IFS="$oIFS";
                [ $1 -ge 6 ] && LSB_DIST=$_dist && DIST_VERSION=$_version && DIST_VERSION_MAJOR=$1
                ;;
            *)
                _error_msg="$_error_msg\nThat is an unsupported distribution."
                ;;
        esac
    fi

    if [ -z "$LSB_DIST" ]; then
        echo >&2 "$(echo | sed "i$_error_msg")"
        echo >&2 ""
        echo >&2 "Please visit the following URL for more detailed installation instructions:"
        echo >&2 ""
        echo >&2 "  https://help.replicated.com/docs/distributing-an-application/installing/"
        exit 1
    fi
}

#######################################
# Detects the init system.
# Upon failure exits with an error.
# Globals:
#   None
# Arguments:
#   None
# Returns:
#   INIT_SYSTEM
#######################################
INIT_SYSTEM=
detectInitSystem() {
    if [[ "$(/sbin/init --version 2>/dev/null)" =~ upstart ]]; then
        INIT_SYSTEM=upstart
    elif [[ "$(systemctl 2>/dev/null)" =~ -\.mount ]]; then
        INIT_SYSTEM=systemd
    elif [ -f /etc/init.d/cron ] && [ ! -h /etc/init.d/cron ]; then
        INIT_SYSTEM=sysvinit
    else
        echo >&2 "Error: failed to detect init system or unsupported."
        exit 1
    fi
}

#######################################
# Finds the init system conf dir. One of /etc/default, /etc/sysconfig
# Globals:
#   None
# Arguments:
#   None
# Returns:
#   CONFDIR
#######################################
CONFDIR=
detectInitSystemConfDir() {
    # NOTE: there was a bug in support bundle that creates a dir in place of non-existant conf files
    if [ -d /etc/default/replicated ] || [ -d /etc/default/replicated-operator ]; then
        if [ -d /etc/default/replicated ]; then
            rm -rf /etc/default/replicated
        fi
        if [ -d /etc/default/replicated-operator ]; then
            rm -rf /etc/default/replicated-operator
        fi
        if [ ! "$(ls -A /etc/default 2>/dev/null)" ]; then
            # directory is empty, probably exists because of support bundle
            rm -rf /etc/default
        fi
    fi
    if [ -d /etc/sysconfig/replicated ] || [ -d /etc/sysconfig/replicated-operator ]; then
        if [ -d /etc/sysconfig/replicated ]; then
            rm -rf /etc/sysconfig/replicated
        fi
        if [ -d /etc/sysconfig/replicated-operator ]; then
            rm -rf /etc/sysconfig/replicated-operator
        fi
        if [ ! "$(ls -A /etc/sysconfig 2>/dev/null)" ]; then
            # directory is empty, probably exists because of support bundle
            rm -rf /etc/sysconfig
        fi
    fi

    # prefer dir if config is already found
    if [ -f /etc/default/replicated ] || [ -f /etc/default/replicated-operator ]; then
        CONFDIR="/etc/default"
    elif [ -f /etc/sysconfig/replicated ] || [ -f /etc/sysconfig/replicated-operator ]; then
        CONFDIR="/etc/sysconfig"
    elif [ "$INIT_SYSTEM" = "systemd" ] && [ -d /etc/sysconfig ]; then
        CONFDIR="/etc/sysconfig"
    else
        CONFDIR="/etc/default"
    fi
    mkdir -p "$CONFDIR"
}

#######################################
# prevent a package from being automatically updated
# Globals:
#   LSB_DIST
# Arguments:
#   None
# Returns:
#   None
#######################################
lockPackageVersion() {
    case $LSB_DIST in
        rhel|centos)
            yum install -y yum-plugin-versionlock
            yum versionlock ${1}-*
            ;;
        ubuntu)
            apt-mark hold $1
            ;;
    esac
}

#######################################
#
# docker.sh
#
# require common.sh, system.sh
#
#######################################

RESTART_DOCKER=0

#######################################
# Prints a message and exits if docker is not installed.
# Globals:
#   None
# Arguments:
#   None
# Returns:
#   None
#######################################
requireDocker() {
    if isDockerInstalled ; then
        return
    fi

    printf "${RED}Docker is not installed. Please install Docker before proceeding.\n" 1>&2
    printf "Instructions for installing Docker can be found at the link below:\n" 1>&2
    printf "\n" 1>&2
    printf "    https://help.replicated.com/community/t/installing-docker-in-airgapped-environments/81${NC}\n" 1>&2
    exit 127
}

#######################################
# Starts docker.
# Globals:
#   LSB_DIST
#   INIT_SYSTEM
# Arguments:
#   None
# Returns:
#   None
#######################################
startDocker() {
    if [ "$LSB_DIST" = "amzn" ]; then
        service docker start
        return
    fi
    case "$INIT_SYSTEM" in
        systemd)
            systemctl enable docker
            systemctl start docker
            ;;
        upstart|sysvinit)
            service docker start
            ;;
    esac
}

#######################################
# Restarts docker.
# Globals:
#   LSB_DIST
#   INIT_SYSTEM
# Arguments:
#   None
# Returns:
#   None
#######################################
restartDocker() {
    case "$INIT_SYSTEM" in
        systemd)
            systemctl daemon-reload
            systemctl restart docker
            ;;
        upstart|sysvinit)
            service docker restart
            ;;
    esac
}

#######################################
# Checks support for docker driver.
# Globals:
#   None
# Arguments:
#   None
# Returns:
#   None
#######################################
checkDockerDriver() {
    if ! isDockerInstalled ; then
        echo >&2 "Error: docker is not installed."
        exit 1
    fi

    if [ "$(ps -ef | grep "docker" | grep -v "grep" | wc -l)" = "0" ]; then
        startDocker
    fi

    _driver=$(docker info 2>/dev/null | grep 'Execution Driver' | awk '{print $3}' | awk -F- '{print $1}')
    if [ "$_driver" = "lxc" ]; then
        echo >&2 "Error: the running Docker daemon is configured to use the '${_driver}' execution driver."
        echo >&2 "This installer only supports the 'native' driver (AKA 'libcontainer')."
        echo >&2 "Check your Docker daemon options."
        exit 1
    fi
}

#######################################
# Checks support for docker storage driver.
# Globals:
#   BYPASS_STORAGEDRIVER_WARNINGS
# Arguments:
#   HARD_FAIL_ON_LOOPBACK
# Returns:
#   None
#######################################
BYPASS_STORAGEDRIVER_WARNINGS=
checkDockerStorageDriver() {
    if [ "$BYPASS_STORAGEDRIVER_WARNINGS" = "1" ]; then
        return
    fi

    if ! isDockerInstalled ; then
        echo >&2 "Error: docker is not installed."
        exit 1
    fi

    if [ "$(ps -ef | grep "docker" | grep -v "grep" | wc -l)" = "0" ]; then
        startDocker
    fi

    _driver=$(docker info 2>/dev/null | grep 'Storage Driver' | awk '{print $3}' | awk -F- '{print $1}')
    if [ "$_driver" = "devicemapper" ] && docker info 2>/dev/null | grep -Fqs 'Data loop file:' ; then
        printf "${RED}The running Docker daemon is configured to use the 'devicemapper' storage driver \
in loopback mode.\nThis is not recommended for production use. Please see to the following URL for more \
information.\n\nhttps://help.replicated.com/docs/kb/developer-resources/devicemapper-warning/.${NC}\n\n\
"
        # HARD_FAIL_ON_LOOPBACK
        if [ -n "$1" ]; then
            printf "${RED}Please configure a recommended storage driver and try again.${NC}\n\n"
            exit 1
        fi

        printf "Do you want to proceed anyway? "
        if ! confirmN; then
            exit 0
        fi
    fi
}

#######################################
# Get the docker group ID.
# Default to 0 for root group.
# Globals:
#   None
# Arguments:
#   None
# Returns:
#   DOCKER_GROUP_ID
#   None
#######################################
DOCKER_GROUP_ID=0
detectDockerGroupId() {
    # Parse the docker group from the docker.sock file
    # On most systems this will be a group called `docker`
    if [ -e /var/run/docker.sock ]; then
        DOCKER_GROUP_ID="$(stat -c '%g' /var/run/docker.sock)"
    # If the docker.sock file doesn't fall back to the docker group.
    elif [ "$(getent group docker)" ]; then
        DOCKER_GROUP_ID="$(getent group docker | cut -d: -f3)"
    fi
}


#######################################
# Check if docker image exists.
# Globals:
#   None
# Arguments:
#   None
# Returns:
#   0 if image exists
#######################################
dockerImageExists() {
    [[ "$(docker images -q "$@" 2> /dev/null)" != "" ]];
}

#######################################
# Gets the image repo tag from the tar file.
# Globals:
#   None
# Arguments:
#   - Path to the tar file
# Returns:
#   REPO_TAG
#######################################
REPO_TAG=
dockerGetRepoTagFromTar() {
    REPO_TAG="$(tar -xOf "$1" manifest.json | sed 's/.*RepoTags":\["\([^"]*\).*/\1/')"
}

#######################################
# Replaces the registry address from a docker repo tag.
# Globals:
#   None
# Arguments:
#   - Repo tag
#   - New registry address
# Returns:
#   REPO_TAG
#######################################
REPO_TAG=
dockerReplaceRegistryAddress() {
    local first
    local rest
    oIFS="$IFS"; IFS="/" read -r first rest <<< "$1"; IFS="$oIFS"
    if [ -z "$rest" ]; then
        # There are no slashes so this is an official image in the official registry.
        REPO_TAG="$2/library/$1"
    elif echo "$rest" | grep -q '/'; then
        REPO_TAG="$2/$rest"
    else
        # NOTE: This makes some assumptions about the domain component vs the org component that
        # are probably not true but it seems good enough for our use case.
        if echo "$first" | grep -q '\.' || echo "$first" | grep -q ':'; then
            # There is probably just no org component here.
            REPO_TAG="$2/$rest"
        else
            # This is the official registry since there is no domain component.
            REPO_TAG="$2/$1"
        fi
    fi
}

#######################################
# Re-tags and pushes image to specified registry.
# Globals:
#   None
# Arguments:
#   - Repo tag
#   - New registry address
# Returns:
#   None
#######################################
REPO_TAG=
dockerRetagAndPushImageToRegistry() {
    dockerReplaceRegistryAddress "$1" "$2"
    local _localTag="$REPO_TAG"
    (set -x; docker tag "$1" "$_localTag")
    (set -x; docker push "$_localTag")
}

#######################################
# Gets the Docker logging driver.
# Globals:
#   None
# Arguments:
#   None
# Returns:
#   DOCKER_LOGGING_DRIVER
#######################################
DOCKER_LOGGING_DRIVER=
dockerGetLoggingDriver() {
    DOCKER_LOGGING_DRIVER="$(docker info 2>/dev/null | grep -i "Logging Driver:" | sed 's/[Ll]ogging [Dd]river: *//')"
}

#######################################
# Gets the docker0 bridge network gateway ip.
# Globals:
#   None
# Arguments:
#   None
# Returns:
#   DOCKER0_GATEWAY_IP
#######################################
get_docker0_gateway_ip() {
    DOCKER0_GATEWAY_IP=$(ip -o -4 address | grep docker0 | awk '{ print $4 }' | cut -d'/' -f1)
    if [ -z "$DOCKER0_GATEWAY_IP" ]; then
        DOCKER0_GATEWAY_IP=172.17.0.1
    fi
}

#######################################
#
# docker-version.sh
#
# require common.sh, system.sh
#
#######################################

#######################################
# Gets docker server version.
# Globals:
#   None
# Arguments:
#   None
# Returns:
#   DOCKER_VERSION
#######################################
DOCKER_VERSION=
getDockerVersion() {
    if ! isDockerInstalled ; then
        return
    fi

    DOCKER_VERSION=$(docker version --format '{{.Server.Version}}' 2>/dev/null || docker -v | awk '{gsub(/,/, "", $3); print $3}')
}

#######################################
# Parses docker version.
# Globals:
#   None
# Arguments:
#   Docker Version
# Returns:
#   DOCKER_VERSION_MAJOR
#   DOCKER_VERSION_MINOR
#   DOCKER_VERSION_PATCH
#   DOCKER_VERSION_RELEASE
#######################################
DOCKER_VERSION_MAJOR=
DOCKER_VERSION_MINOR=
DOCKER_VERSION_PATCH=
DOCKER_VERSION_RELEASE=
parseDockerVersion() {
    # reset
    DOCKER_VERSION_MAJOR=
    DOCKER_VERSION_MINOR=
    DOCKER_VERSION_PATCH=
    DOCKER_VERSION_RELEASE=
    if [ -z "$1" ]; then
        return
    fi

    OLD_IFS="$IFS" && IFS=. && set -- $1 && IFS="$OLD_IFS"
    DOCKER_VERSION_MAJOR=$1
    DOCKER_VERSION_MINOR=$2
    OLD_IFS="$IFS" && IFS=- && set -- $3 && IFS="$OLD_IFS"
    DOCKER_VERSION_PATCH=$1
    DOCKER_VERSION_RELEASE=$2
}

#######################################
# Compare two docker versions ignoring the patch version.
# Returns -1 if A lt B, 0 if eq, 1 A gt B.
# Globals:
#   None
# Arguments:
#   Docker Version A
#   Docker Version B
# Returns:
#   COMPARE_DOCKER_VERSIONS_RESULT
#######################################
COMPARE_DOCKER_VERSIONS_RESULT=
compareDockerVersionsIgnorePatch() {
    # reset
    COMPARE_DOCKER_VERSIONS_RESULT=
    parseDockerVersion "$1"
    _a_major="$DOCKER_VERSION_MAJOR"
    _a_minor="$DOCKER_VERSION_MINOR"
    parseDockerVersion "$2"
    _b_major="$DOCKER_VERSION_MAJOR"
    _b_minor="$DOCKER_VERSION_MINOR"
    if [ "$_a_major" -lt "$_b_major" ]; then
        COMPARE_DOCKER_VERSIONS_RESULT=-1
        return
    fi
    if [ "$_a_major" -gt "$_b_major" ]; then
        COMPARE_DOCKER_VERSIONS_RESULT=1
        return
    fi
    if [ "$_a_minor" -lt "$_b_minor" ]; then
        COMPARE_DOCKER_VERSIONS_RESULT=-1
        return
    fi
    if [ "$_a_minor" -gt "$_b_minor" ]; then
        COMPARE_DOCKER_VERSIONS_RESULT=1
        return
    fi
    COMPARE_DOCKER_VERSIONS_RESULT=0
}

#######################################
# Compare two docker versions.
# Returns -1 if A lt B, 0 if eq, 1 A gt B.
# Globals:
#   None
# Arguments:
#   Docker Version A
#   Docker Version B
# Returns:
#   COMPARE_DOCKER_VERSIONS_RESULT
#######################################
COMPARE_DOCKER_VERSIONS_RESULT=
compareDockerVersions() {
    # reset
    COMPARE_DOCKER_VERSIONS_RESULT=
    compareDockerVersionsIgnorePatch "$1" "$2"
    if [ "$COMPARE_DOCKER_VERSIONS_RESULT" -ne "0" ]; then
        return
    fi
    parseDockerVersion "$1"
    _a_patch="$DOCKER_VERSION_PATCH"
    parseDockerVersion "$2"
    _b_patch="$DOCKER_VERSION_PATCH"
    if [ "$_a_patch" -lt "$_b_patch" ]; then
        COMPARE_DOCKER_VERSIONS_RESULT=-1
        return
    fi
    if [ "$_a_patch" -gt "$_b_patch" ]; then
        COMPARE_DOCKER_VERSIONS_RESULT=1
        return
    fi
    COMPARE_DOCKER_VERSIONS_RESULT=0
}

#######################################
# Get max docker version for lsb dist/version.
# Globals:
#   LSB_DIST
#   DIST_VERSION_MAJOR
#   DIST_VERSION
# Arguments:
#   None
# Returns:
#   MAX_DOCKER_VERSION_RESULT
#######################################
MAX_DOCKER_VERSION_RESULT=
getMaxDockerVersion() {
    MAX_DOCKER_VERSION_RESULT=

    # Max Docker version on CentOS 6 is 1.7.1.
    if [ "$LSB_DIST" = "centos" ]; then
        if [ "$DIST_VERSION_MAJOR" = "6" ]; then
            MAX_DOCKER_VERSION_RESULT="1.7.1"
        fi
    fi
    # Max Docker version on RHEL 6 is 1.7.1.
    if [ "$LSB_DIST" = "rhel" ]; then
        if [ "$DIST_VERSION_MAJOR" = "6" ]; then
            MAX_DOCKER_VERSION_RESULT="1.7.1"
        fi
    fi
    if [ "$LSB_DIST" = "ubuntu" ]; then
        # Max Docker version on Ubuntu 14.04 is 18.06.1.
        # see https://github.com/docker/for-linux/issues/591
        if [ "$DIST_VERSION" = "14.04" ]; then
            MAX_DOCKER_VERSION_RESULT="18.06.1"
        # Max Docker version on Ubuntu 16.04 is 19.03.8.
        elif [ "$DIST_VERSION" = "16.04" ]; then
            MAX_DOCKER_VERSION_RESULT="19.03.8"
        fi
    fi
    if [ "$LSB_DIST" = "debian" ]; then
        # Max Docker version on Debian 7 is 18.03.1
        if [ "$DIST_VERSION" = "7" ]; then
            MAX_DOCKER_VERSION_RESULT="18.03.1"
        # Max Docker version on Debian 8 is 18.06.2.
        elif [ "$DIST_VERSION" = "8" ]; then
            MAX_DOCKER_VERSION_RESULT="18.06.2"
        # Max Docker version on Debian 9 is 19.03.8.
        elif [ "$DIST_VERSION" = "9" ]; then
            MAX_DOCKER_VERSION_RESULT="19.03.8"
        fi
    fi
    # 2020-05-11
    # Max Docker version on SUSE Linux Enterprise Server 12 and 15 is 19.03.5.
    if [ "$LSB_DIST" = "sles" ]; then
        MAX_DOCKER_VERSION_RESULT="19.03.5"
    fi
    # Max Docker version on Oracle Linux 6.x seems to be 17.05.0.
    if [ "$LSB_DIST" = "ol" ]; then
        if [ "$DIST_VERSION_MAJOR" = "6" ]; then
            MAX_DOCKER_VERSION_RESULT="17.05.0"
        fi
    fi
}

#######################################
# Get min docker version for lsb dist/version.
# Globals:
#   LSB_DIST
#   DIST_VERSION_MAJOR
#   DIST_VERSION
# Arguments:
#   None
# Returns:
#   MIN_DOCKER_VERSION_RESULT
#######################################
MIN_DOCKER_VERSION_RESULT=
getMinDockerVersion() {
    MIN_DOCKER_VERSION_RESULT=

    if [ "$LSB_DIST" = "ubuntu" ]; then
        # Min Docker version on Ubuntu 20.04 is 19.03.9.
        if [ "$DIST_VERSION" = "20.04" ]; then
            MIN_DOCKER_VERSION_RESULT="19.03.11"
        fi
    fi

    if [ "$LSB_DIST" = "centos" ] || [ "$LSB_DIST" = "rhel" ] || [ "$LSB_DIST" = "ol" ]; then
        # Min Docker version on RHEL/CentOS/OL 8.x is 20.10.7
        if [ "$DIST_VERSION_MAJOR" = "8" ]; then
            MIN_DOCKER_VERSION_RESULT="20.10.7"
        fi
    fi

}

#######################################
#
# docker-install.sh
#
# require common.sh, prompt.sh, system.sh, docker-version.sh
#
#######################################

#######################################
# Installs requested docker version.
# Requires at least min docker version to proceed.
# Globals:
#   LSB_DIST
#   INIT_SYSTEM
#   AIRGAP
# Arguments:
#   Requested Docker Version
#   Minimum Docker Version
# Returns:
#   DID_INSTALL_DOCKER
#######################################
DID_INSTALL_DOCKER=0
installDocker() {
    _dockerGetBestVersion "$1"

    if ! isDockerInstalled ; then
        _dockerRequireMinInstallableVersion "$2"
        _installDocker "$BEST_DOCKER_VERSION_RESULT" 1
        return
    fi

    getDockerVersion

    compareDockerVersions "$DOCKER_VERSION" "$2"
    if [ "$COMPARE_DOCKER_VERSIONS_RESULT" -eq "-1" ]; then
        _dockerRequireMinInstallableVersion "$2"
        _dockerForceUpgrade "$BEST_DOCKER_VERSION_RESULT"
    else
        compareDockerVersions "$DOCKER_VERSION" "$BEST_DOCKER_VERSION_RESULT"
        if [ "$COMPARE_DOCKER_VERSIONS_RESULT" -eq "-1" ]; then
            _dockerUpgrade "$BEST_DOCKER_VERSION_RESULT"
            if [ "$DID_INSTALL_DOCKER" -ne "1" ]; then
                _dockerProceedAnyway "$BEST_DOCKER_VERSION_RESULT"
            fi
        elif [ "$COMPARE_DOCKER_VERSIONS_RESULT" -eq "1" ]; then
            # allow patch versions greater than the current version
            compareDockerVersionsIgnorePatch "$DOCKER_VERSION" "$BEST_DOCKER_VERSION_RESULT"
            if [ "$COMPARE_DOCKER_VERSIONS_RESULT" -eq "1" ]; then
                _dockerProceedAnyway "$BEST_DOCKER_VERSION_RESULT"
            fi
        fi
        # The system has the exact pinned version installed.
        # No need to run the Docker install script.
    fi
}

#######################################
# Install docker from a prepared image
# Globals:
#   LSB_DIST
#   INIT_SYSTEM
# Returns:
#   DID_INSTALL_DOCKER
#######################################
DID_INSTALL_DOCKER=0
installDockerOffline() {
    if isDockerInstalled ; then
        return
    fi

    case "$LSB_DIST$DIST_VERSION" in
        ubuntu16.04)
            mkdir -p image/
            layer_id=$(tar xvf packages-docker-ubuntu1604.tar -C image/ | grep layer.tar | cut -d'/' -f1)
            tar xvf image/${layer_id}/layer.tar
            pushd archives/
               dpkg -i --force-depends-version *.deb
            popd
            DID_INSTALL_DOCKER=1
            return
            ;;
        ubuntu18.04)
            mkdir -p image/
            layer_id=$(tar xvf packages-docker-ubuntu1804.tar -C image/ | grep layer.tar | cut -d'/' -f1)
            tar xvf image/${layer_id}/layer.tar
            pushd archives/
               dpkg -i --force-depends-version *.deb
            popd
            DID_INSTALL_DOCKER=1
            return
            ;;
        centos7.4|centos7.5|centos7.6|centos7.7|centos7.8|centos7.9|rhel7.4|rhel7.5|rhel7.6|rhel7.7|rhel7.8|rhel7.9)
            mkdir -p image/
            layer_id=$(tar xvf packages-docker-rhel7.tar -C image/ | grep layer.tar | cut -d'/' -f1)
            tar xvf image/${layer_id}/layer.tar
            pushd archives/
                rpm --upgrade --force --nodeps *.rpm
            popd
            systemctl enable docker
            systemctl start docker
            DID_INSTALL_DOCKER=1
            return
            ;;
        *)
   esac

   printf "Offline Docker install is not supported on ${LSB_DIST} ${DIST_MAJOR}"
   exit 1
}

_installDocker() {
    _should_skip_docker_ee_install
    if [ "$SHOULD_SKIP_DOCKER_EE_INSTALL" -eq "1" ]; then
        printf "${RED}Enterprise Linux distributions require Docker Enterprise Edition. Please install Docker before running this installation script.${NC}\n" 1>&2
        exit 1
    fi

    if [ "$LSB_DIST" = "amzn" ]; then
        # Docker install script no longer supports Amazon Linux
        printf "${YELLOW}Pinning Docker version not supported on Amazon Linux${NC}\n"
        printf "${GREEN}Installing Docker from Yum repository${NC}\n"

        # 2021-11-16
        # Amazon Linux has very specific Docker versions available.
        # We support 17.12.1, 18.06.1, 18.09.9 and 20.10.7-3.
        compareDockerVersions "18.0.0" "${1}"
        if [ "$COMPARE_DOCKER_VERSIONS_RESULT" -eq "-1" ]; then
            compareDockerVersions "19.0.0" "${1}"
            if [ "$COMPARE_DOCKER_VERSIONS_RESULT" -le "0" ]; then
                ( set -x; yum install -y -q docker-20.10.7-3.amzn2 || yum install -y docker-20.10.7-3.amzn2 || \
                    yum install -y -q docker || yum install -y -q docker )
            else
                compareDockerVersions "18.09.0" "${1}"
                if [ "$COMPARE_DOCKER_VERSIONS_RESULT" -le "0" ]; then
                    if commandExists "amazon-linux-extras"; then
                        ( set -x; amazon-linux-extras install -y -q docker=18.09.9 || amazon-linux-extras install -y docker=18.09.9 || \
                            amazon-linux-extras install -y -q docker || amazon-linux-extras install -y docker )
                    else
                        ( set -x; yum install -y -q docker-18.09.9ce || yum install -y -q docker )
                    fi
                else
                    if commandExists "amazon-linux-extras"; then
                        ( set -x; amazon-linux-extras install -y -q docker=18.06.1 || amazon-linux-extras install -y docker=18.06.1 || \
                            amazon-linux-extras install -y -q docker || amazon-linux-extras install -y docker )
                    else
                        ( set -x; yum install -y -q docker-18.06.1ce || yum install -y -q docker )
                    fi
                fi
            fi
        else
            if commandExists "amazon-linux-extras"; then
                ( set -x; amazon-linux-extras install -y -q docker=17.12.1 || amazon-linux-extras install docker=17.12.1 \
                    || amazon-linux-extras install -y -q docker || amazon-linux-extras install docker )
            else
                ( set -x; yum install -y -q docker-17.12.1ce || yum install -y -q docker )
            fi
        fi

        service docker start || true
        DID_INSTALL_DOCKER=1
        return
    elif [ "$LSB_DIST" = "sles" ]; then
        printf "${YELLOW}Pinning Docker version not supported on SUSE Linux${NC}\n"
        printf "${GREEN}Installing Docker from Zypper repository${NC}\n"

        # 2020-05-11
        # SUSE has Docker 17.09.1_ce, 18.09.7_ce and 19.03.5 available.
        compareDockerVersions "19.0.0" "${1}"
        if [ "$COMPARE_DOCKER_VERSIONS_RESULT" -eq "-1" ]; then
            ( set -x; zypper -n install "docker=19.03.5_ce" || zypper -n install docker )
        else
            compareDockerVersions "18.0.0" "${1}"
            if [ "$COMPARE_DOCKER_VERSIONS_RESULT" -eq "-1" ]; then
                ( set -x; zypper -n install "docker=18.09.7_ce" || zypper -n install docker )
            else
                ( set -x; zypper -n install "docker=17.09.1_ce" || zypper -n install docker )
            fi
        fi

        service docker start || true
        DID_INSTALL_DOCKER=1
        return
    fi

    compareDockerVersions "17.06.0" "${1}"
    if { [ "$LSB_DIST" = "rhel" ] || [ "$LSB_DIST" = "ol" ] ; } && [ "$COMPARE_DOCKER_VERSIONS_RESULT" -le "0" ]; then
        if yum list installed "container-selinux" >/dev/null 2>&1; then
            # container-selinux installed
            printf "Skipping install of container-selinux as a version of it was already present\n"
        else
            # Install container-selinux from official source, ignoring errors
            logStep "Installing container-selinux"
            yum install -y container-selinux 2> /dev/null || true
            # verify installation success
            if yum list installed "container-selinux" >/dev/null 2>&1; then
                logSuccess "Installed container-selinux from existing sources"
            else
                if [ "$DIST_VERSION" = "7.6" ]; then
                    # Install container-selinux from mirror.centos.org
                    yum install -y "http://mirror.centos.org/centos/7/extras/x86_64/Packages/container-selinux-2.107-1.el7_6.noarch.rpm"
                    if yum list installed "container-selinux" >/dev/null 2>&1; then
                        logWarn "Installed package required by docker container-selinux from fallback source of mirror.centos.org"
                    else
                        bail "Failed to install container-selinux package, required by Docker CE. Please install the container-selinux package or Docker before running this installation script."
                    fi
                else
                    # Install container-selinux from mirror.centos.org
                    yum install -y "http://mirror.centos.org/centos/7/extras/x86_64/Packages/container-selinux-2.107-3.el7.noarch.rpm"
                    if yum list installed "container-selinux" >/dev/null 2>&1; then
                        logWarn "Installed package required by docker container-selinux from fallback source of mirror.centos.org"
                    else
                        bail "Failed to install container-selinux package, required by Docker CE. Please install the container-selinux package or Docker before running this installation script."
                    fi
                fi
            fi
        fi
    fi

    _docker_install_url="https://get.replicated.com/docker-install.sh"
    printf "${GREEN}Installing docker version ${1} from ${_docker_install_url}${NC}\n"
    getUrlCmd
    $URLGET_CMD "$_docker_install_url?docker_version=${1}&lsb_dist=${LSB_DIST}&dist_version=${DIST_VERSION_MAJOR}" > "$REPLICATED_TEMP_DIR/docker_install.sh"
    # When this script is piped into bash as stdin, apt-get will eat the remaining parts of this script,
    # preventing it from being executed.  So using /dev/null here to change stdin for the docker script.
    VERSION="${1}" sh "$REPLICATED_TEMP_DIR/docker_install.sh" < /dev/null

    printf "${GREEN}External script is finished${NC}\n"

    # Need to manually start Docker in these cases
    if [ "$INIT_SYSTEM" = "systemd" ]; then
        systemctl enable docker
        systemctl start docker
    elif [ "$LSB_DIST" = "centos" ] && [ "$DIST_VERSION_MAJOR" = "6" ]; then
        service docker start
    elif [ "$LSB_DIST" = "rhel" ] && [ "$DIST_VERSION_MAJOR" = "6" ]; then
        service docker start
    fi

    # i guess the second arg means to skip this?
    if [ "$2" -eq "1" ]; then
        # set +e because df --output='fstype' doesn't exist on older versions of rhel and centos
        set +e
        _maybeRequireRhelDevicemapper
        set -e
    fi

    DID_INSTALL_DOCKER=1
}

_maybeRequireRhelDevicemapper() {
    # If the distribution is CentOS or RHEL and the filesystem is XFS, it is possible that docker has installed with overlay as the device driver
    # In that case we should change the storage driver to devicemapper, because while loopback-lvm is slow it is also more likely to work
    if { [ "$LSB_DIST" = "centos" ] || [ "$LSB_DIST" = "rhel" ] ; } && { df --output='fstype' 2>/dev/null | grep -q -e '^xfs$' || grep -q -e ' xfs ' /etc/fstab ; } ; then
        # If distribution is centos or rhel and filesystem is XFS

        # xfs (RHEL 7.2 and higher), but only with d_type=true enabled. Use xfs_info to verify that the ftype option is set to 1.
        # https://docs.docker.com/storage/storagedriver/overlayfs-driver/#prerequisites
        oIFS="$IFS"; IFS=.; set -- $DIST_VERSION; IFS="$oIFS";
        _dist_version_minor=$2
        if [ "$DIST_VERSION_MAJOR" -eq "7" ] && [ "$_dist_version_minor" -ge "2" ] && xfs_info / | grep -q -e 'ftype=1'; then
            return
        fi

        # Get kernel version (and extract major+minor version)
        kernelVersion="$(uname -r)"
        semverParse $kernelVersion

        if docker info | grep -q -e 'Storage Driver: overlay2\?' && { ! xfs_info / | grep -q -e 'ftype=1' || [ $major -lt 3 ] || { [ $major -eq 3 ] && [ $minor -lt 18 ]; }; }; then
            # If storage driver is overlay and (ftype!=1 OR kernel version less than 3.18)
            printf "${YELLOW}Changing docker storage driver to devicemapper."
            printf "Using overlay/overlay2 requires CentOS/RHEL 7.2 or higher and ftype=1 on xfs filesystems.\n"
            printf "It is recommended to configure devicemapper to use direct-lvm mode for production.${NC}\n"
            systemctl stop docker

            insertOrReplaceJsonParam /etc/docker/daemon.json storage-driver devicemapper

            systemctl start docker
        fi
    fi
}

_dockerUpgrade() {
    _should_skip_docker_ee_install
    if [ "$SHOULD_SKIP_DOCKER_EE_INSTALL" -eq "1" ]; then
        return
    fi

    if [ "$AIRGAP" != "1" ]; then
        printf "This installer will upgrade your current version of Docker (%s) to the recommended version: %s\n" "$DOCKER_VERSION" "$1"
        printf "Do you want to allow this? "
        if confirmY; then
            _installDocker "$1" 0
            return
        fi
    fi
}

_dockerForceUpgrade() {
    if [ "$AIRGAP" -eq "1" ]; then
        echo >&2 "Error: The installed version of Docker ($DOCKER_VERSION) may not be compatible with this installer."
        echo >&2 "Please manually upgrade your current version of Docker to the recommended version: $1"
        exit 1
    fi

    _dockerUpgrade "$1"
    if [ "$DID_INSTALL_DOCKER" -ne "1" ]; then
        printf "Please manually upgrade your current version of Docker to the recommended version: %s\n" "$1"
        exit 0
    fi
}

_dockerProceedAnyway() {
    printf "The installed version of Docker (%s) may not be compatible with this installer.\nThe recommended version is %s\n" "$DOCKER_VERSION" "$1"
    printf "Do you want to proceed anyway? "
    if ! confirmN; then
        exit 0
    fi
}

_dockerGetBestVersion() {
    BEST_DOCKER_VERSION_RESULT="$1"
    getMinDockerVersion
    if [ -n "$MIN_DOCKER_VERSION_RESULT" ]; then
        compareDockerVersions "$MIN_DOCKER_VERSION_RESULT" "$BEST_DOCKER_VERSION_RESULT"
        if [ "$COMPARE_DOCKER_VERSIONS_RESULT" -eq "1" ]; then
            BEST_DOCKER_VERSION_RESULT="$MIN_DOCKER_VERSION_RESULT"
            return
        fi
    fi
    getMaxDockerVersion
    if [ -n "$MAX_DOCKER_VERSION_RESULT" ]; then
        compareDockerVersions "$BEST_DOCKER_VERSION_RESULT" "$MAX_DOCKER_VERSION_RESULT"
        if [ "$COMPARE_DOCKER_VERSIONS_RESULT" -eq "1" ]; then
            BEST_DOCKER_VERSION_RESULT="$MAX_DOCKER_VERSION_RESULT"
            return
        fi
    fi
}

_dockerRequireMinInstallableVersion() {
    getMaxDockerVersion
    if [ -z "$MAX_DOCKER_VERSION_RESULT" ]; then
        return
    fi

    compareDockerVersions "$1" "$MAX_DOCKER_VERSION_RESULT"
    if [ "$COMPARE_DOCKER_VERSIONS_RESULT" -eq "1" ]; then
        echo >&2 "Error: This install script may not be compatible with this linux distribution."
        echo >&2 "We have detected a maximum docker version of $MAX_DOCKER_VERSION_RESULT while the required minimum version for this script is $1."
        exit 1
    fi
}

#######################################
# Checks if Docker EE should be installed or upgraded.
# Globals:
#   LSB_DIST
#   NO_CE_ON_EE
# Returns:
#   SHOULD_SKIP_DOCKER_EE_INSTALL
#######################################
SHOULD_SKIP_DOCKER_EE_INSTALL=
_should_skip_docker_ee_install() {
  SHOULD_SKIP_DOCKER_EE_INSTALL=
  if [ "$LSB_DIST" = "rhel" ] || [ "$LSB_DIST" = "ol" ] || [ "$LSB_DIST" = "sles" ]; then
      if [ -n "$NO_CE_ON_EE" ]; then
          SHOULD_SKIP_DOCKER_EE_INSTALL=1
          return
      fi
  fi
  SHOULD_SKIP_DOCKER_EE_INSTALL=0
}


#######################################
# Docker uses cgroupfs by default to manage cgroup. On distributions using systemd,
# i.e. RHEL and Ubuntu, this causes issues because there are now 2 seperate ways
# to manage resources. For more info see the link below.
# https://github.com/kubernetes/kubeadm/issues/1394#issuecomment-462878219
# Globals:
#   None
# Returns:
#   None
#######################################
changeCgroupDriverToSystemd() {
    insertJSONArray "/etc/docker/daemon.json" "exec-opts" "native.cgroupdriver=systemd"
}

command_exists() {
    command -v "$@" > /dev/null 2>&1
}

installAliasFile() {
    if [ -n "$bashrc_file" ]; then
        if ! grep -q "/etc/replicated.alias" "$bashrc_file"; then
            cat >> "$bashrc_file" <<- EOM

if [ -f /etc/replicated.alias ]; then
    . /etc/replicated.alias
fi
EOM
        fi
    fi
}

ask_for_ip() {
    local count=0
    local regex="^[[:digit:]]+: ([^[:space:]]+)[[:space:]]+[[:alnum:]]+ ([[:digit:].]+)"
    local iface_names
    local iface_addrs
    local line
    while read -r line; do
        let "count += 1"
        [[ $line =~ $regex ]]
        iface_names[$((count-1))]=${BASH_REMATCH[1]}
        iface_addrs[$((count-1))]=${BASH_REMATCH[2]}
    done <<< "$(ip -4 -o addr)"
    if [[ $count -eq 0 ]]; then
        printf "The installer couldn't discover any valid network interfaces on this machine.\n"
        printf "Check your network configuration and re-run this script again.\n"
        printf "If you want to skip this discovery process, pass the 'local_address' arg to this script, e.g. 'sudo ./install.sh local_address=1.2.3.4'\n"
        exit 1
    elif [[ $count -eq 1 ]]; then
        PRIVATE_ADDRESS=${iface_addrs[1]}
        printf "\nThe installer will use network interface '%s' (with IP address '%s')\n\n" ${iface_names[1]} ${iface_addrs[1]}
        return
    fi
    printf "The installer was unable to automatically detect the private IP address of this machine.\n"
    printf "Please choose one of the following network interfaces:\n"
    printf "[0] default: unspecified\n"
    for i in $(seq 0 $((count-1))); do
        printf "[%d] %-5s\t%s\n" $((i+1)) ${iface_names[$i]} ${iface_addrs[$i]}
    done
    while true; do
        printf "Enter desired number (0-%d): " $count
        set +e
        if [[ -n "$READ_TIMEOUT" ]]; then
            read -t 60 chosen < /dev/tty
        else
            read chosen < /dev/tty
        fi
        set -e
        if [[ -z "$chosen" || $chosen -eq 0 ]]; then
            return
        fi
        if [[ $chosen -gt 0 && $chosen -le $count ]]; then
            PRIVATE_ADDRESS=${iface_addrs[$((chosen-1))]}
            printf "\nThe installer will use network interface '%s' (with IP address '%s).\n\n" ${iface_names[$((chosen-1))]} $PRIVATE_ADDRESS
            return
        fi
    done
}

discover_ip() {
    if [[ -n "$PRIVATE_ADDRESS" ]]; then
        return
    fi

    echo "Checking network configuration..."
    local gce_test=$(curl --connect-timeout 10 -s -o /dev/null -w '%{http_code}' -H 'Metadata-Flavor: Google' http://169.254.169.254/computeMetadata/v1/instance/network-interfaces/0/access-configs/0/external-ip)
    if [[ "$gce_test" != "200" ]]; then
        echo "Analyzing network configuration..."
        local ec2_test=$(curl --connect-timeout 10 -s -o /dev/null -w '%{http_code}' http://169.254.169.254/latest/meta-data/public-ipv4)
        if [[ "$ec2_test" != "200" ]]; then
            ask_for_ip
        fi
    fi
}

ask_for_proxy() {
    printf "\nDoes this machine require a proxy to access the Internet? (y/N) "
    set +e
    read $READ_TIMEOUT wants_proxy < /dev/tty
    set -e
    if [[ "$wants_proxy" != "y" && "$wants_proxy" != "Y" ]]; then
        return
    fi

    printf "Enter desired HTTP proxy address: "
    set +e
    read $READ_TIMEOUT chosen < /dev/tty
    set -e
    if [[ -n "$chosen" ]]; then
        PROXY_ADDRESS="$chosen"
        printf "\nThe installer will use the proxy at '%s'.\n\n" "$PROXY_ADDRESS"
    fi
}

discover_proxy() {
    if [[ -n "$PROXY_ADDRESS" ]]; then
        return
    fi

    if [[ -n $HTTP_PROXY ]]; then
        PROXY_ADDRESS="$HTTP_PROXY"
        printf "\nThe installer will use the proxy at '%s'. (imported from env var 'HTTP_PROXY')\n\n" $PROXY_ADDRESS
        return
    elif [[ -n $http_proxy ]]; then
        PROXY_ADDRESS="$http_proxy"
        printf "\nThe installer will use the proxy at '%s'. (imported from env var 'http_proxy')\n\n" $PROXY_ADDRESS
        return
    elif [[ -n $HTTPS_PROXY ]]; then
        PROXY_ADDRESS="$HTTPS_PROXY"
        printf "\nThe installer will use the proxy at '%s'. (imported from env var 'HTTPS_PROXY')\n\n" $PROXY_ADDRESS
        return
    elif [[ -n $https_proxy ]]; then
        PROXY_ADDRESS="$https_proxy"
        printf "\nThe installer will use the proxy at '%s'. (imported from env var 'https_proxy')\n\n" $PROXY_ADDRESS
        return
    fi

    ask_for_proxy
}

create_replicated_conf() {
    if [[ ! -f /etc/replicated.conf ]]; then
        printf "{\n" > /etc/replicated.conf

        if [[ $nodocker -eq 1 ]]; then
            printf '\t"AgentBootstrapInstallsDocker": false,\n' >> /etc/replicated.conf
        fi
        if [[ -n "$PROXY_ADDRESS" ]]; then
            printf "\t\"HttpProxy\": \"%s\",\n" "$PROXY_ADDRESS" >> /etc/replicated.conf
        fi
        if [[ -n "$PRIVATE_ADDRESS" ]]; then
            printf "\t\"LocalAddress\": \"%s\",\n" "$PRIVATE_ADDRESS" >> /etc/replicated.conf
        fi
        printf '\t"ReleaseChannel": "stable"\n' >> /etc/replicated.conf
        printf '}\n' >> /etc/replicated.conf
    fi
}

outro() {
    if [[ -z "$PRIVATE_ADDRESS" ]]; then
        PRIVATE_ADDRESS="<this_server_address>"
    fi

    printf "\nTo continue the installation, visit the following URL in your browser: https://%s:8800\n\n" "$PRIVATE_ADDRESS"
}

do_install() {
    maybeCreateTempDir

    case "$(uname -m)" in
        *64)
            ;;
        *)
            echo >&2 'Error: you are not using a 64bit platform.'
            echo >&2 'This installer currently only supports 64bit platforms.'
            exit 1
            ;;
    esac

    user="$(id -un 2>/dev/null || true)"

    if [[ "$user" != "root" ]]; then
        echo >&2 "This script requires admin privileges. Please re-run it as root."
        exit 1
    fi

    if [ -f /etc/bashrc ]; then
        bashrc_file="/etc/bashrc"
    elif [ -f /etc/bash.bashrc ]; then
        bashrc_file="/etc/bash.bashrc"
    else
        echo >&2 'No global bashrc file found.  Admin command aliasing will be disabled.'
    fi

    detectLsbDist
    detectInitSystem

    lsb_dist="$LSB_DIST"

    cat > /etc/logrotate.d/replicated  <<-EOF
/var/log/replicated/*.log {
  size 500k
  rotate 4
  nocreate
  compress
  notifempty
  missingok
}
EOF

    # Check for proxy
    discover_proxy
    if [[ -n "$PROXY_ADDRESS" ]]; then
        export http_proxy=${PROXY_ADDRESS}
        export https_proxy=${PROXY_ADDRESS}
        export HTTP_PROXY=${PROXY_ADDRESS}
        export HTTPS_PROXY=${PROXY_ADDRESS}
    fi

    discover_ip

    case "$lsb_dist" in
        fedora|rhel|centos|amzn)
            yum install -y gpg

            gpg --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 822819BB
            gpg --export -a 822819BB > "$REPLICATED_TEMP_DIR/replicated_pub.asc"
            rpm --import "$REPLICATED_TEMP_DIR/replicated_pub.asc"

            cat > /etc/yum.repos.d/replicated.repo <<-EOF
[replicated]
name=Replicated Repository
baseurl=https://get.replicated.com/yum/stable
EOF

            # Enable secondary repos and install deps.
            yum install -y yum-utils
            case "$lsb_dist" in
                amzn)
                    yum-config-manager --enable epel/x86_64
                    ;;
                rhel)
                    set +e
                    subscription-manager repos --enable rhel-7-server-extras-rpms
                    if [[ $? -ne 0 ]]; then
                        yum-config-manager --enable rhui-REGION-rhel-server-extras
                    fi
                    set -e
                    ;;
            esac
            yum makecache
            yum install -y python-hashlib curl

            if [ $SKIP_DOCKER_INSTALL -eq 0 ]; then
                installDocker "$PINNED_DOCKER_VERSION" "$MIN_DOCKER_VERSION"
                checkDockerDriver
            fi

            create_replicated_conf

            yum install -y replicated replicated-ui

            case "$lsb_dist" in
                amzn)
                    # Amazon Linux uses upstart.
                    # Except for Docker, which uses sysv init
                    service docker start
                    start replicated
                    start replicated-ui
                    ;;
                *)
                    systemctl enable docker
                    systemctl enable replicated
                    systemctl enable replicated-ui

                    printf "\nStarting services. Please wait, this may take a few minutes...\n"

                    systemctl start docker
                    systemctl start replicated
                    systemctl start replicated-ui
                    ;;
            esac

            installAliasFile

            outro

            exit 0
            ;;

        ubuntu|debian)
            if [ $SKIP_DOCKER_INSTALL -eq 0 ]; then
                installDocker "$PINNED_DOCKER_VERSION" "$MIN_DOCKER_VERSION"
                checkDockerDriver
            fi

            create_replicated_conf

            export DEBIAN_FRONTEND=noninteractive

            did_apt_get_update=
            apt_get_update() {
                if [ -z "$did_apt_get_update" ]; then
                    apt-get update
                    did_apt_get_update=1
                fi
            }

            if [ ! -e /usr/lib/apt/methods/https ]; then
                apt_get_update
                apt-get install -y apt-transport-https
            fi

            apt_get_update
            apt-get install -y curl

            echo "deb https://get.replicated.com/apt all stable" | sudo tee /etc/apt/sources.list.d/replicated.list
            apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 68386EDB2C8B75CA615A8C985D4781862AFFAC40

            apt-get update # forced
            apt-get install -y replicated replicated-ui

            if command_exists systemctl; then
                systemctl enable docker
                systemctl enable replicated
                systemctl enable replicated-ui
            fi

            installAliasFile

            outro

            exit 0
            ;;
    esac

    cat >&2 <<'EOF'

  Either your platform is not easily detectable, is not supported by this
  installer script, or does not yet have a package for Replicated.
  Please visit the following URL for more detailed installation instructions:

    http://docs.replicated.com/docs/installing-replicated

EOF
    exit 1
}

do_install "$@"

%INSTALL_SCRIPT_END%
)

PROXY_ADDRESS=
PRIVATE_ADDRESS=
SKIP_DOCKER_INSTALL=0
READ_TIMEOUT="-t 20"

while [ "$1" != "" ]; do
    PARAM=`echo "$1" | awk -F= '{print $1}'`
    VALUE=`echo "$1" | awk -F= '{print $2}'`
    case $PARAM in
        http-proxy|http_proxy)
            PROXY_ADDRESS=$VALUE
            ;;
        local-address|local_address)
            PRIVATE_ADDRESS=$VALUE
            ;;
        no-docker|no_docker)
            SKIP_DOCKER_INSTALL=1
            ;;
        no-auto|no_auto)
            READ_TIMEOUT=
            ;;
        *)
            echo "ERROR: unknown parameter \"$PARAM\""
            exit 1
            ;;
    esac
    shift
done

SCRIPT=$(echo "$SCRIPT" | sed 's|(%proxy_address%)|'"$PROXY_ADDRESS"'|' | sed 's|(%private_address%)|'"$PRIVATE_ADDRESS"'|' | sed 's|(%skip_docker_install%)|'"$SKIP_DOCKER_INSTALL"'|' | sed 's|(%read_timeout%)|'"$READ_TIMEOUT"'|')

echo "$SCRIPT" | /usr/bin/env bash -s