#!/usr/bin/env bash
# netdata
# real-time performance and health monitoring, done right!
# (C) 2017 Costa Tsaousis <costa@tsaousis.gr>
# SPDX-License-Identifier: GPL-3.0-or-later

# Exit code: 0 - Success
# Exit code: 1 - Unknown argument
# Exit code: 2 - Problems with claiming working directory
# Exit code: 3 - Missing dependencies
# Exit code: 4 - Failure to connect to endpoint
# Exit code: 5 - Unknown HTTP error message
# Exit code: 6 - The CLI didn't work
# Exit code: 7 - Wrong user
#
# OK: Agent claimed successfully
# HTTP Status code: 204
# Exit code: 0
#
# Error: The agent id is invalid; it does not fulfill the constraints
# HTTP Status code: 422
# Error key: "ErrInvalidNodeID"
# Error message: "invalid node id"
# Exit code: 6

# Error: The agent hostname is invalid; it does not fulfill the constraints
# HTTP Status code: 422
# Error key: "ErrInvalidNodeName"
# Error message: "invalid node name"
# Exit code: 7
#
# Error: At least one of the given rooms ids is invalid; it does not fulfill the constraints
# HTTP Status code: 422
# Error key: "ErrInvalidRoomID"
# Error message: "invalid room id"
# Exit code: 8
#
# Error: Invalid public key; the public key is empty or not present
# HTTP Status code: 422
# Error key: "ErrInvalidPublicKey"
# Error message: "invalid public key"
# Exit code: 9
#
# Error: Expired, missing or invalid token
# HTTP Status code: 403
# Error key: "ErrForbidden"
# Error message: "token expired" | "token not found" | "invalid token"
# Exit code: 10
#
# Error: Duplicate agent id; an agent with the same id is already registered in the cloud
# HTTP Status code: 409
# Error key: "ErrAlreadyClaimed"
# Error message: "already claimed"
# Exit code: 11
#
# Error: The node claiming process is still in progress.
# HTTP Status code: 102
# Error key: "ErrProcessingClaim"
# Error message: "processing claiming"
# Exit code: 12
#
# Error: Internal server error. Any other unexpected error (DB problems, etc.)
# HTTP Status code: 500
# Error key: "ErrInternalServerError"
# Error message: "Internal Server Error"
# Exit code: 13
#
# Error: There was a timout processing the claim.
# HTTP Status code: 504
# Error key: "ErrGatewayTimeout"
# Error message: "Gateway Timeout"
# Exit code: 14
#
# Error: The service cannot handle the claiming request at this time.
# HTTP Status code: 503
# Error key: "ErrServiceUnavailable"
# Error message: "Service Unavailable"
# Exit code: 15

if command -v curl >/dev/null 2>&1 ; then
        URLTOOL="curl"
elif command -v wget >/dev/null 2>&1 ; then
        URLTOOL="wget"
else
        echo >&2 "I need curl or wget to proceed, but neither is available on this system."
        exit 3
fi
if ! command -v openssl >/dev/null 2>&1 ; then
        echo >&2 "I need openssl to proceed, but it is not available on this system."
        exit 3
fi


# -----------------------------------------------------------------------------
# defaults to allow running this script by hand

[ -z "${NETDATA_USER_CONFIG_DIR}" ] && NETDATA_USER_CONFIG_DIR="/etc/netdata"
MACHINE_GUID_FILE="/var/lib/netdata/registry/netdata.public.unique.id"
CLAIMING_DIR="${NETDATA_USER_CONFIG_DIR}/claim.d"
TOKEN="unknown"
URL_BASE="https://netdata.cloud"
ID="unknown"
ROOMS=""
HOSTNAME=$(hostname)
CLOUD_CERTIFICATE_FILE="${CLAIMING_DIR}/cloud_fullchain.pem"
VERBOSE=0
INSECURE=0
RELOAD=1
NETDATA_USER=netdata
[ -z "$EUID" ] && EUID="$(id -u)"

CONF_USER=$(grep '^[ 	#]*run as user[ 	]*=' "${NETDATA_USER_CONFIG_DIR}/netdata.conf" 2>/dev/null)
if [ -n "$CONF_USER" ]; then
    NETDATA_USER=$(echo "$CONF_USER" | sed 's/^[^=]*=[ \t]*//' | sed 's/[ \t]*$//')
fi


# get the MACHINE_GUID by default
if [ -r "${MACHINE_GUID_FILE}" ]; then
        ID="$(cat "${MACHINE_GUID_FILE}")"
fi

# get token from file
if [ -r "${CLAIMING_DIR}/token" ]; then
        TOKEN="$(cat "${CLAIMING_DIR}/token")"
fi

# get rooms from file
if [ -r "${CLAIMING_DIR}/rooms" ]; then
        ROOMS="$(cat "${CLAIMING_DIR}/rooms")"
fi

for arg in "$@"
do
        case $arg in
                -token=*) TOKEN=${arg:7} ;;
                -url=*) URL_BASE=${arg:5} ;;
                -id=*) ID=${arg:4} ;;
                -rooms=*) ROOMS=${arg:7} ;;
                -hostname=*) HOSTNAME=${arg:10} ;;
                -verbose) VERBOSE=1 ;;
                -insecure) INSECURE=1 ;;
                -proxy=*) PROXY=${arg:7} ;;
                -noproxy) NOPROXY=yes ;;
                -noreload) RELOAD=0 ;;
                -user=*) NETDATA_USER=${arg:6} ;;
                *)  echo >&2 "Unknown argument ${arg}"
                    exit 1 ;;
        esac
        shift 1
done

if [ "$EUID" != "0" ] && [ "$(whoami)" != "$NETDATA_USER" ]; then
    echo >&2 "This script must be run by the $NETDATA_USER user account"
    exit 7
fi

# if curl not installed give warning SOCKS can't be used
if [[ "${URLTOOL}" != "curl" && "${PROXY:0:5}" = socks ]] ; then
        echo >&2 "wget doesn't support SOCKS. Please install curl or disable SOCKS proxy."
        exit 1
fi

echo >&2 "Token: ****************"
echo >&2 "Base URL: $URL_BASE"
echo >&2 "Id: $ID"
echo >&2 "Rooms: $ROOMS"
echo >&2 "Hostname: $HOSTNAME"
echo >&2 "Proxy: $PROXY"
echo >&2 "Netdata user: $NETDATA_USER"

# create the claiming directory for this user
if [ ! -d "${CLAIMING_DIR}" ] ; then
        mkdir -p "${CLAIMING_DIR}" && chmod 0770 "${CLAIMING_DIR}"
# shellcheck disable=SC2181
        if [ $? -ne 0 ] ; then
                echo >&2 "Failed to create claiming working directory ${CLAIMING_DIR}"
                exit 2
        fi
fi
if [ ! -w "${CLAIMING_DIR}" ] ; then
        echo >&2 "No write permission in claiming working directory ${CLAIMING_DIR}"
        exit 2
fi

if [ ! -f "${CLAIMING_DIR}/private.pem" ] ; then
        echo >&2 "Generating private/public key for the first time."
        if ! openssl genrsa -out "${CLAIMING_DIR}/private.pem" 2048 ; then
                echo >&2 "Failed to generate private/public key pair."
                exit 2
        fi
fi
if [ ! -f "${CLAIMING_DIR}/public.pem" ] ; then
        echo >&2 "Extracting public key from private key."
        if ! openssl rsa -in "${CLAIMING_DIR}/private.pem" -outform PEM -pubout -out "${CLAIMING_DIR}/public.pem" ; then
                echo >&2 "Failed to extract public key."
                exit 2
        fi
fi

TARGET_URL="${URL_BASE%/}/api/v1/spaces/nodes/${ID}"
# shellcheck disable=SC2002
KEY=$(cat "${CLAIMING_DIR}/public.pem" | tr '\n' '!' | sed -e 's/!/\\n/g')
# shellcheck disable=SC2001
[ -n "$ROOMS" ] && ROOMS=\"$(echo "$ROOMS" | sed s'/,/", "/g')\"

cat > "${CLAIMING_DIR}/tmpin.txt" <<EMBED_JSON
{
    "node": {
        "id": "$ID",
        "hostname": "$HOSTNAME"
    },
    "token": "$TOKEN",
    "rooms" : [ $ROOMS ],
    "publicKey" : "$KEY"
}
EMBED_JSON

if [ "${VERBOSE}" == 1 ] ; then
    echo "Request to server:"
    cat "${CLAIMING_DIR}/tmpin.txt"
fi


if [ "${URLTOOL}" = "curl" ] ; then
        URLCOMMAND="curl --connect-timeout 5 --retry 0 -s -i -X PUT -d \"@${CLAIMING_DIR}/tmpin.txt\""
        if [ "${NOPROXY}" = "yes" ] ; then
                URLCOMMAND="${URLCOMMAND} -x \"\""
        elif [ -n "${PROXY}" ] ; then
                URLCOMMAND="${URLCOMMAND} -x \"${PROXY}\""
        fi
else
        URLCOMMAND="wget -T 15 -O -  -q --save-headers --content-on-error=on --method=PUT \
        --body-file=\"${CLAIMING_DIR}/tmpin.txt\""
        if [ "${NOPROXY}" = "yes" ] ; then
                URLCOMMAND="${URLCOMMAND} --no-proxy"
        elif [ "${PROXY:0:4}" = http ] ; then
                URLCOMMAND="export http_proxy=${PROXY}; ${URLCOMMAND}"
        fi
fi

if [ "${INSECURE}" == 1 ] ; then
    if [ "${URLTOOL}" = "curl" ] ; then
        URLCOMMAND="${URLCOMMAND} --insecure"
    else
        URLCOMMAND="${URLCOMMAND} --no-check-certificate"
    fi
fi

if [ -r "${CLOUD_CERTIFICATE_FILE}" ] ; then
        if [ "${URLTOOL}" = "curl" ] ; then
                URLCOMMAND="${URLCOMMAND} --cacert \"${CLOUD_CERTIFICATE_FILE}\""
        else
                URLCOMMAND="${URLCOMMAND} --ca-certificate \"${CLOUD_CERTIFICATE_FILE}\""
        fi
fi

if [ "${VERBOSE}" == 1 ]; then
    echo "${URLCOMMAND} \"${TARGET_URL}\""
fi
eval "${URLCOMMAND} \"${TARGET_URL}\"" >"${CLAIMING_DIR}/tmpout.txt"
URLCOMMAND_EXIT_CODE=$?
if [ "${URLTOOL}" = "wget" ] && [ "${URLCOMMAND_EXIT_CODE}" -eq 8 ] ; then
# We consider the server issuing an error response a successful attempt at communicating
        URLCOMMAND_EXIT_CODE=0
fi

rm -f "${CLAIMING_DIR}/tmpin.txt"

# Check if URLCOMMAND connected and received reply
if [ "${URLCOMMAND_EXIT_CODE}" -ne 0 ] ; then
        echo >&2 "Failed to connect to ${URL_BASE}, return code ${URLCOMMAND_EXIT_CODE}"
        rm -f "${CLAIMING_DIR}/tmpout.txt"
        exit 4
fi

if [ "${VERBOSE}" == 1 ] ; then
    echo "Response from server:"
    cat "${CLAIMING_DIR}/tmpout.txt"
fi

HTTP_STATUS_CODE=$(grep "HTTP" "${CLAIMING_DIR}/tmpout.txt" | awk -F " " '{print $2}')

if [ "${HTTP_STATUS_CODE}" = "204" ] ; then
        rm -f "${CLAIMING_DIR}/tmpout.txt"
        echo -n "${ID}" >"${CLAIMING_DIR}/claimed_id" || (echo >&2 "Claiming failed"; set -e; exit 2)
        rm -f "${CLAIMING_DIR}/token" || (echo >&2 "Claiming failed"; set -e; exit 2)
        if [ "$EUID" == "0" ]; then
            chown -R "${NETDATA_USER}:${NETDATA_USER}" ${CLAIMING_DIR} || (echo >&2 "Claiming failed"; set -e; exit 2)
        fi
        if [ "${RELOAD}" == "0" ] ; then
            exit 0
        fi
        netdatacli reload-claiming-state && echo >&2 "Node was successfully claimed." && exit 0
        echo "The claim was successful but the agent could not be notified ($?)- it requires a restart to connect to the cloud"
        exit 6
fi

ERROR_MESSAGE=$(grep "\"errorMsgKey\":" "${CLAIMING_DIR}/tmpout.txt" | awk -F "errorMsgKey\":\"" '{print $2}' | awk -F "\"" '{print $1}')
case ${ERROR_MESSAGE} in
        "ErrInvalidNodeID") EXIT_CODE=6 ;;
        "ErrInvalidNodeName") EXIT_CODE=7 ;;
        "ErrInvalidRoomID") EXIT_CODE=8 ;;
        "ErrInvalidPublicKey") EXIT_CODE=9 ;;
        "ErrForbidden") EXIT_CODE=10 ;;
        "ErrAlreadyClaimed") EXIT_CODE=11 ;;
        "ErrProcessingClaim") EXIT_CODE=12 ;;
        "ErrInternalServerError") EXIT_CODE=13 ;;
        "ErrGatewayTimeout") EXIT_CODE=14 ;;
        "ErrServiceUnavailable") EXIT_CODE=15 ;;
        *) EXIT_CODE=5 ;;
esac
echo >&2 "Failed to claim node."
rm -f "${CLAIMING_DIR}/tmpout.txt"
exit $EXIT_CODE
