#!/bin/bash set -euo pipefail # Use REPO_SERVER to avoid clashing with the system/container HOSTNAME. REPO_SERVER="${REPO_SERVER:-repo.emqx.works}" REPO_NAME="${REPO_NAME:-emqx-enterprise}" GPG_KEY_URL="https://${REPO_SERVER}/gpg.key" DOWNLOAD_BASE_URL="${DOWNLOAD_BASE_URL:-https://www.emqx.com/en/downloads/enterprise}" # INSTALL_METHOD can be: auto, deb, rpm, zip INSTALL_METHOD="${INSTALL_METHOD:-auto}" # macOS install layout: # - INSTALL_ROOT has highest priority if explicitly set # - otherwise use ROOT/emqx # - fallback to /usr/local/emqx ROOT="${ROOT:-/usr/local}" INSTALL_ROOT="${INSTALL_ROOT:-${ROOT}/emqx}" BIN_LINK_DIR="${BIN_LINK_DIR:-/usr/local/bin}" TMP_DIR="$(mktemp -d)" cleanup() { rm -rf "${TMP_DIR}" } trap cleanup EXIT die() { echo "$*" >&2 exit 1 } require_cmd() { if ! command -v "$1" >/dev/null 2>&1; then die "Missing required command: $1" fi } run_as_root() { if [ "$(id -u)" -eq 0 ]; then "$@" return fi if ! command -v sudo >/dev/null 2>&1; then die "This action requires root privileges, and sudo is not available." fi sudo "$@" } detect_install_method() { if [ "${INSTALL_METHOD}" != "auto" ]; then case "${INSTALL_METHOD}" in deb|rpm|zip) echo "${INSTALL_METHOD}" return ;; *) die "Unsupported INSTALL_METHOD=${INSTALL_METHOD}. Use auto, deb, rpm, or zip." ;; esac fi if [ "$(uname -s)" = "Darwin" ]; then echo "zip" return fi if command -v apt-get >/dev/null 2>&1; then echo "deb" return fi if command -v dnf >/dev/null 2>&1 || command -v yum >/dev/null 2>&1; then echo "rpm" return fi if [ -e /etc/os-release ]; then # shellcheck disable=SC1091 . /etc/os-release case "${ID:-}" in debian|ubuntu|linuxmint|pop|raspbian) echo "deb" return ;; rhel|centos|rocky|almalinux|amzn|ol|fedora) echo "rpm" return ;; esac fi die "Unable to determine install method for this system." } detect_deb_dist() { local os dist if [ -e /etc/os-release ]; then # shellcheck disable=SC1091 . /etc/os-release os="${ID:-}" dist="${VERSION_CODENAME:-}" if [ -z "${dist}" ] && [ -n "${VERSION:-}" ]; then dist="$(echo "${VERSION}" | awk -F'[)(]+' '{print $2}')" fi elif command -v lsb_release >/dev/null 2>&1; then os="$(lsb_release -i | awk '{ print tolower($3) }')" dist="$(lsb_release -c | awk '{ print $2 }')" else die "Unable to determine Debian/Ubuntu codename." fi if [ -z "${dist}" ]; then die "Unable to determine Debian/Ubuntu codename." fi DEB_OS="${os}" DEB_DIST="${dist}" } install_deb_repo() { require_cmd apt-get detect_deb_dist echo "Detected Debian-based system: ${DEB_OS}/${DEB_DIST}" run_as_root apt-get update >/dev/null if ! command -v curl >/dev/null 2>&1; then run_as_root apt-get install -y curl >/dev/null fi if ! command -v gpg >/dev/null 2>&1; then run_as_root apt-get install -y gnupg >/dev/null fi if [ "${DEB_OS}" = "debian" ]; then run_as_root apt-get install -y debian-archive-keyring >/dev/null || true fi run_as_root apt-get install -y apt-transport-https >/dev/null || true local keyrings_dir keyring_path source_path key_tmp keyrings_dir="/etc/apt/keyrings" keyring_path="${keyrings_dir}/emqx-archive-keyring.gpg" source_path="/etc/apt/sources.list.d/${REPO_NAME}.list" key_tmp="${TMP_DIR}/emqx-archive-keyring.gpg" run_as_root mkdir -p "${keyrings_dir}" curl -fsSL "${GPG_KEY_URL}" | gpg --dearmor > "${key_tmp}" run_as_root install -m 0644 "${key_tmp}" "${keyring_path}" run_as_root rm -f /etc/apt/trusted.gpg.d/emqx_emqx-enterprise.gpg printf 'deb [signed-by=%s] https://%s/apt/%s %s main\n' \ "${keyring_path}" "${REPO_SERVER}" "${REPO_NAME}" "${DEB_DIST}" \ | run_as_root tee "${source_path}" >/dev/null run_as_root apt-get update >/dev/null echo "Repository configured: ${source_path}" echo "Install package with: apt-get install ${REPO_NAME}" } detect_rpm_dist() { local os dist if [ -e /etc/os-release ]; then # shellcheck disable=SC1091 . /etc/os-release os="${ID:-}" dist="${VERSION_ID:-}" else die "Unable to determine RPM distro/version." fi if [ -z "${os}" ] || [ -z "${dist}" ]; then die "Unable to determine RPM distro/version." fi RPM_OS="${os}" if [ "${RPM_OS}" = "amzn" ]; then RPM_DIST="${dist}" else RPM_DIST="${dist%%.*}" fi } rpm_pkg_manager() { if command -v dnf >/dev/null 2>&1; then echo "dnf" return fi if command -v yum >/dev/null 2>&1; then echo "yum" return fi die "Neither dnf nor yum is available." } install_rpm_repo() { detect_rpm_dist local pkg_mgr pkg_mgr="$(rpm_pkg_manager)" echo "Detected RPM-based system: ${RPM_OS}/${RPM_DIST} (${pkg_mgr})" if ! command -v curl >/dev/null 2>&1; then run_as_root "${pkg_mgr}" install -y curl >/dev/null fi local baseurl repo_tmp repo_path if [ "${RPM_OS}" = "amzn" ]; then baseurl="https://${REPO_SERVER}/rpm/${REPO_NAME}/amzn/${RPM_DIST}/\$basearch" else baseurl="https://${REPO_SERVER}/rpm/${REPO_NAME}/el/${RPM_DIST}/\$basearch" fi repo_tmp="${TMP_DIR}/${REPO_NAME}.repo" repo_path="/etc/yum.repos.d/${REPO_NAME}.repo" cat > "${repo_tmp}" < (bv[i] + 0)) return 1 if ((av[i] + 0) < (bv[i] + 0)) return 0 } return 0 } { gsub(/^v/, "", $0) if (best == "" || gt($0, best)) best = $0 } END { if (best != "") print "v" best } ')" if [ -z "${version}" ]; then die "Failed to detect latest EMQX Enterprise version." fi echo "${version}" } pick_macos_package() { local version="$1" local target_macos pkg target_macos="$(sw_vers -productVersion | cut -d. -f1)" pkg="$(curl -fsSL "${DOWNLOAD_BASE_URL}/${version}" \ | grep -Eo "emqx-enterprise-${version#v}-macos[0-9]+-arm64\\.zip" \ | sort -u \ | awk -v target="${target_macos}" ' { if (match($0, /macos[0-9]+-arm64/)) { major = substr($0, RSTART + 5, RLENGTH - 11) + 0 if (major == target) { exact = $0 } else if (major < target) { if (major > lower_major) { lower = $0 lower_major = major } } else { if (upper == "" || major < upper_major) { upper = $0 upper_major = major } } } } END { if (exact != "") print exact else if (lower != "") print lower else print upper } ' \ | head -n 1)" if [ -z "${pkg}" ]; then die "No macOS arm64 package found for ${version}." fi echo "${pkg}" } verify_checksum() { local file="$1" local checksum_file="$2" local expected actual expected="$(grep -Eo '[A-Fa-f0-9]{64}' "${checksum_file}" | head -n 1 | tr '[:upper:]' '[:lower:]')" if [ -z "${expected}" ]; then die "Failed to parse SHA256 from ${checksum_file}" fi actual="$(shasum -a 256 "${file}" | awk '{print tolower($1)}')" if [ "${actual}" != "${expected}" ]; then echo "Checksum mismatch for ${file}" >&2 echo "Expected: ${expected}" >&2 echo "Actual: ${actual}" >&2 exit 1 fi } extract_archive() { local archive_file="$1" local extract_dir="$2" mkdir -p "${extract_dir}" require_cmd unzip unzip -q "${archive_file}" -d "${extract_dir}" } pick_source_dir() { local extract_dir="$1" local entry_count nested_dir if [ -f "${extract_dir}/bin/emqx" ] && [ -d "${extract_dir}/releases" ]; then echo "${extract_dir}" return fi entry_count="$(find "${extract_dir}" -mindepth 1 -maxdepth 1 | wc -l | tr -d '[:space:]')" if [ "${entry_count}" = "1" ]; then nested_dir="$(find "${extract_dir}" -mindepth 1 -maxdepth 1 -type d | head -n 1)" if [ -n "${nested_dir}" ] && [ -f "${nested_dir}/bin/emqx" ] && [ -d "${nested_dir}/releases" ]; then echo "${nested_dir}" return fi fi die "Unexpected package layout: could not find EMQX root (expected bin/emqx and releases/)." } install_macos_zip() { if [ "$(uname -s)" != "Darwin" ]; then die "zip mode only supports macOS." fi if [ "$(uname -m)" != "arm64" ]; then die "Only Apple Silicon (arm64) is supported by this installer." fi require_cmd curl require_cmd grep require_cmd awk require_cmd shasum require_cmd ditto local version version_url package package_url sha_url archive_file checksum_file extract_dir source_dir target_dir version="${EMQX_VERSION:-$(latest_version)}" version_url="${DOWNLOAD_BASE_URL}/${version}" package="$(pick_macos_package "${version}")" package_url="${version_url}/${package}" sha_url="${package_url}.sha256" archive_file="${TMP_DIR}/${package}" checksum_file="${archive_file}.sha256" extract_dir="${TMP_DIR}/extract" target_dir="${INSTALL_ROOT}/${version#v}" echo "Latest version: ${version}" echo "Package: ${package}" echo "Downloading package..." curl -fL --retry 3 -o "${archive_file}" "${package_url}" echo "Downloading checksum..." curl -fL --retry 3 -o "${checksum_file}" "${sha_url}" verify_checksum "${archive_file}" "${checksum_file}" extract_archive "${archive_file}" "${extract_dir}" source_dir="$(pick_source_dir "${extract_dir}")" run_as_root mkdir -p "${INSTALL_ROOT}" run_as_root rm -rf "${target_dir}" run_as_root mkdir -p "${target_dir}" run_as_root ditto "${source_dir}" "${target_dir}" run_as_root ln -sfn "${target_dir}" "${INSTALL_ROOT}/current" if [ -f "${target_dir}/bin/emqx" ]; then run_as_root mkdir -p "${BIN_LINK_DIR}" run_as_root ln -sfn "${target_dir}/bin/emqx" "${BIN_LINK_DIR}/emqx" echo "Linked ${BIN_LINK_DIR}/emqx -> ${target_dir}/bin/emqx" else echo "Installed, but could not locate ${target_dir}/bin/emqx for PATH linking." fi echo echo "EMQX Enterprise ${version} installed to ${target_dir}" echo "Current symlink: ${INSTALL_ROOT}/current" } main() { local method method="$(detect_install_method)" case "${method}" in deb) install_deb_repo ;; rpm) install_rpm_repo ;; zip) install_macos_zip ;; *) die "Unhandled install method: ${method}" ;; esac } main "$@"