#!/usr/bin/bash
#
# NetIntel Community License Installer
#
# Requests a Community Edition license from the production License Portal,
# downloads the issued license artifact, verifies its SHA-256, and installs it as:
#   /etc/netintel/license/license.bin
#
# Expected production installation:
#   owner: root
#   group: netintel
#   mode: 0640
#
# The NetIntel package is responsible for creating the netintel user/group.
#

set -euo pipefail

PORTAL_BASE_URL="${NETINTEL_LICENSE_PORTAL_URL:-https://license.gnsoftlab.com}"
LICENSE_DIR="/etc/netintel/license"
LICENSE_PATH="${LICENSE_DIR}/license.bin"
TMP_PATH=""
RESPONSE_PATH=""

usage() {
    cat <<'EOF_USAGE'
Usage:
  install-netintel-license.sh
  install-netintel-license.sh --help

Requests a NetIntel Community Edition license from the License Portal,
then downloads and installs the issued license artifact.

Options:
  --portal-url URL    Override the production portal base URL.
  --help              Show this help.

Environment:
  NETINTEL_LICENSE_PORTAL_URL
                      Override the portal base URL.
                      Default: https://license.gnsoftlab.com

The portal-issued download token is kept internal to the script and is not
shown on screen or passed as a command-line argument.
EOF_USAGE
}

die() {
    echo "ERROR: $*" >&2
    exit 1
}

cleanup() {
    if [[ -n "${TMP_PATH}" && -f "${TMP_PATH}" ]]; then
        rm -f -- "${TMP_PATH}"
    fi

    if [[ -n "${RESPONSE_PATH}" && -f "${RESPONSE_PATH}" ]]; then
        rm -f -- "${RESPONSE_PATH}"
    fi
}

trap cleanup EXIT

while [[ $# -gt 0 ]]; do
    case "$1" in
        --portal-url)
            [[ $# -ge 2 ]] || die "--portal-url requires a value"
            PORTAL_BASE_URL="${2%/}"
            shift 2
            ;;
        --help|-h)
            usage
            exit 0
            ;;
        *)
            die "unknown option: $1"
            ;;
    esac
done

[[ $EUID -eq 0 ]] || die "run this installer as root (for example: sudo $0)"

command -v curl >/dev/null 2>&1 || die "curl is required"
command -v install >/dev/null 2>&1 || die "install is required"
command -v getent >/dev/null 2>&1 || die "getent is required"
command -v sha256sum >/dev/null 2>&1 || die "sha256sum is required"
command -v python3 >/dev/null 2>&1 || die "python3 is required"

getent passwd netintel >/dev/null 2>&1 || \
    die "netintel user does not exist; install the NetIntel package first"

getent group netintel >/dev/null 2>&1 || \
    die "netintel group does not exist; install the NetIntel package first"

case "${PORTAL_BASE_URL}" in
    https://*)
        ;;
    *)
        die "portal URL must use HTTPS"
        ;;
esac

read -r -p "Organization: " ORGANIZATION
read -r -p "Contact name: " CONTACT
read -r -p "Email: " EMAIL

[[ -n "${ORGANIZATION}" ]] || die "organization must not be empty"
[[ -n "${CONTACT}" ]] || die "contact name must not be empty"
[[ -n "${EMAIL}" ]] || die "email must not be empty"

# Create the target directory before creating temporary files there.
install -d -o root -g netintel -m 0750 "${LICENSE_DIR}"

RESPONSE_PATH="$(mktemp)"
chmod 0600 "${RESPONSE_PATH}"

ISSUE_URL="${PORTAL_BASE_URL}/api/v1/licenses/community"

# JSON is constructed by Python so customer-provided values are safely escaped.
REQUEST_JSON="$(
    ORGANIZATION="${ORGANIZATION}" \
    CONTACT="${CONTACT}" \
    EMAIL="${EMAIL}" \
    python3 - <<'PY'
import json
import os

print(json.dumps({
    "organization": os.environ["ORGANIZATION"],
    "contact": os.environ["CONTACT"],
    "email": os.environ["EMAIL"],
}))
PY
)"

echo
echo "Requesting NetIntel Community license..."

HTTP_STATUS="$(
    curl \
        --silent \
        --show-error \
        --location \
        --proto '=https' \
        --tlsv1.2 \
        --connect-timeout 10 \
        --max-time 30 \
        --header 'Content-Type: application/json' \
        --output "${RESPONSE_PATH}" \
        --write-out '%{http_code}' \
        --request POST \
        --data "${REQUEST_JSON}" \
        "${ISSUE_URL}"
)"

if [[ ! "${HTTP_STATUS}" =~ ^2[0-9][0-9]$ ]]; then
    echo "Portal response (${HTTP_STATUS}):" >&2
    cat "${RESPONSE_PATH}" >&2
    die "license issuance request failed"
fi

[[ -s "${RESPONSE_PATH}" ]] || \
    die "license portal returned an empty response"

# Extract and validate the metadata returned by the issuance endpoint.
readarray -t LICENSE_METADATA < <(
    python3 - "${RESPONSE_PATH}" <<'PY'
import json
import re
import sys

path = sys.argv[1]

try:
    with open(path, "r", encoding="utf-8") as f:
        data = json.load(f)
except Exception as exc:
    raise SystemExit(f"invalid JSON response: {exc}")

license_id = data.get("license_id")
download_url = data.get("download_url")
download_token = data.get("download_token")
artifact_sha256 = data.get("artifact_sha256")

if not isinstance(license_id, str) or not re.fullmatch(r"[0-9]+", license_id):
    raise SystemExit("portal response has invalid license_id")

if not isinstance(download_url, str) or not download_url.startswith("/"):
    raise SystemExit("portal response has invalid download_url")

if not isinstance(download_token, str) or not download_token:
    raise SystemExit("portal response has missing download_token")

if not isinstance(artifact_sha256, str) or \
        not re.fullmatch(r"[0-9a-fA-F]{64}", artifact_sha256):
    raise SystemExit("portal response has invalid artifact_sha256")

print(license_id)
print(download_url)
print(download_token)
print(artifact_sha256)
PY
)

[[ "${#LICENSE_METADATA[@]}" -eq 4 ]] || \
    die "unexpected license portal response"

LICENSE_ID="${LICENSE_METADATA[0]}"
DOWNLOAD_URL_PATH="${LICENSE_METADATA[1]}"
DOWNLOAD_TOKEN="${LICENSE_METADATA[2]}"
EXPECTED_SHA256="${LICENSE_METADATA[3]}"

DOWNLOAD_URL="${PORTAL_BASE_URL}${DOWNLOAD_URL_PATH}"

case "${DOWNLOAD_URL}" in
    https://*)
        ;;
    *)
        die "license download URL must use HTTPS"
        ;;
esac

TMP_PATH="$(mktemp "${LICENSE_DIR}/.license.bin.XXXXXX")"
chmod 0600 "${TMP_PATH}"

echo "License issued successfully."
echo "License ID: ${LICENSE_ID}"
echo "Downloading license artifact..."

curl \
    --fail \
    --silent \
    --show-error \
    --location \
    --proto '=https' \
    --tlsv1.2 \
    --connect-timeout 10 \
    --max-time 30 \
    --header "Authorization: Bearer ${DOWNLOAD_TOKEN}" \
    --output "${TMP_PATH}" \
    "${DOWNLOAD_URL}"

[[ -s "${TMP_PATH}" ]] || \
    die "downloaded license artifact is empty"

ACTUAL_SHA256="$(sha256sum "${TMP_PATH}" | awk '{print $1}')"

if [[ "${ACTUAL_SHA256,,}" != "${EXPECTED_SHA256,,}" ]]; then
    die "SHA-256 mismatch: expected ${EXPECTED_SHA256}, got ${ACTUAL_SHA256}"
fi

echo "SHA-256 verified: ${ACTUAL_SHA256}"

install \
    -o root \
    -g netintel \
    -m 0640 \
    "${TMP_PATH}" \
    "${LICENSE_PATH}"

rm -f -- "${TMP_PATH}"
TMP_PATH=""

echo
echo "NetIntel Community license installed successfully."
echo "License: ${LICENSE_PATH}"
echo "Owner:   root:netintel"
echo "Mode:    0640"
echo "SHA-256: ${ACTUAL_SHA256}"
echo
echo "Restart NetIntel to load the newly installed license:"
echo "  sudo systemctl restart netintel"
