From bb781e648c74bacbe2739c49c1a37a906448116c Mon Sep 17 00:00:00 2001 From: David Schroeder Date: Sat, 25 Jul 2026 14:51:42 -0500 Subject: [PATCH] update --- README.md | 8 ++- defaults.inc | 7 +- proxmenu-scripts.sh | 168 ++++++++++++++++++++++++++++---------------- 3 files changed, 116 insertions(+), 67 deletions(-) diff --git a/README.md b/README.md index cf55475..bf00873 100644 --- a/README.md +++ b/README.md @@ -48,9 +48,11 @@ keepalived Deploy Keepalived across the cluster ## Companion files Large installer artifacts used by this project are stored in the private -`TAI/files` package registry. SentinelOne installation requires a temporary -deployment code from TAPM Deployment Access; the private Gitea credential is -never stored on or returned to a Proxmox host. +`TAI/files` package registry. SentinelOne, RMM, Acronis, and ScreenConnect +installation require a temporary deployment code from TAPM Deployment Access. +The private Gitea credentials are never stored on or returned to a Proxmox +host. SentinelOne package versions can be updated through the deployment +portal without changing ProxMenu. ## Runtime requirements diff --git a/defaults.inc b/defaults.inc index 770baa2..abb25cc 100755 --- a/defaults.inc +++ b/defaults.inc @@ -3,7 +3,7 @@ action="${1:-}" FOLDER='/opt/idssys/ta-proxmenu' -VERS='2026.7.25-19' +VERS='2026.7.25-20' noupdate=' ' @@ -25,10 +25,9 @@ if [[ -z "$VIRTIO_DOWNLOAD_URL" ]]; then fi VIRTIO_FILE="${VIRTIO_DOWNLOAD_URL##*/}" -S1_VERSION='26_1_1_31' -S1_PACKAGE="SentinelAgent_linux_x86_64_v${S1_VERSION}.deb" -S1_BROKER_URL='https://tapm.scity.us' +TAPM_BROKER_URL='https://tapm.scity.us' S1_BROKER_PACKAGE='sentinelone-linux' +S1_PACKAGE='tapm-sentinelone.deb' if [[ -d /mnt/pve/PVE-Shared-Storage/template/iso ]]; then DLDIR='/mnt/pve/PVE-Shared-Storage/template/iso' diff --git a/proxmenu-scripts.sh b/proxmenu-scripts.sh index 882bdea..3825af8 100755 --- a/proxmenu-scripts.sh +++ b/proxmenu-scripts.sh @@ -14,6 +14,91 @@ FINISH_ACTION() { ENTER2CONTINUE } +TAPM_CLEAR_AUTHORIZATION() { + unset TAPM_SESSION_TOKEN TAPM_PACKAGE_URL TAPM_PACKAGE_SHA256 TAPM_PACKAGE_VERSION +} + +TAPM_AUTHORIZE() { + local required_action="${1:-}" + local required_package="${2:-}" + local authorization_label="${3:-this installation}" + local deploycode exchange_response host_fingerprint + local -a access + + TAPM_CLEAR_AUTHORIZATION + if ! command -v python3 >/dev/null 2>&1; then + echo -e "${idsCL[LightRed]}Python 3 is required to authorize ${authorization_label}.${idsCL[Default]}" + return 1 + fi + echo + echo -en "${idsCL[LightYellow]}Paste the TAPM deployment code: ${idsCL[Default]}" + read -r -s deploycode + echo + deploycode="${deploycode^^}" + if [[ ! "$deploycode" =~ ^TAPM-[0-9A-HJKMNP-TV-Z]{5}-[0-9A-HJKMNP-TV-Z]{5}$ ]]; then + unset deploycode + echo -e "${idsCL[LightRed]}The TAPM deployment code is not valid.${idsCL[Default]}" + return 1 + fi + + host_fingerprint="$(sha256sum /etc/machine-id | cut -d' ' -f1)" + exchange_response="$( + TAPM_CODE="$deploycode" TAPM_FINGERPRINT="$host_fingerprint" TAPM_HOSTNAME="$(hostname)" \ + TAPM_REQUESTED_ACTION="$required_action" TAPM_REQUESTED_PACKAGE="$required_package" \ + python3 -c 'import json, os, sys; json.dump({"code": os.environ["TAPM_CODE"], "host_fingerprint": os.environ["TAPM_FINGERPRINT"], "hostname": os.environ["TAPM_HOSTNAME"], "requested_action": os.environ["TAPM_REQUESTED_ACTION"], "requested_package": os.environ["TAPM_REQUESTED_PACKAGE"]}, sys.stdout)' | + curl --fail --silent --show-error \ + --header 'Content-Type: application/json' \ + --data-binary @- "${TAPM_BROKER_URL}/api/v1/exchange" + )" || { + unset deploycode host_fingerprint exchange_response + echo -e "${idsCL[LightRed]}TAPM could not authorize ${authorization_label}.${idsCL[Default]}" + return 1 + } + unset deploycode host_fingerprint + + mapfile -t access < <( + printf '%s' "$exchange_response" | + TAPM_REQUIRED_ACTION="$required_action" TAPM_REQUIRED_PACKAGE="$required_package" \ + python3 -c ' +import json, os, sys +data = json.load(sys.stdin) +required_action = os.environ["TAPM_REQUIRED_ACTION"] +required_package = os.environ["TAPM_REQUIRED_PACKAGE"] +if required_action and required_action not in data.get("actions", []): + raise SystemExit(1) +package = None +if required_package: + package = next((item for item in data.get("packages", []) if item.get("slug") == required_package), None) + if not package: + raise SystemExit(1) +print(data.get("session_token", "")) +print(package.get("download_url", "") if package else "") +print(package.get("sha256", "") if package else "") +print(package.get("version", "") if package else "") +' + ) || true + unset exchange_response + + if [[ ${#access[@]} -ne 4 || -z "${access[0]}" ]]; then + unset access + echo -e "${idsCL[LightRed]}This deployment code does not authorize ${authorization_label}.${idsCL[Default]}" + return 1 + fi + if [[ -n "$required_package" && + ( -z "${access[1]}" || ! "${access[2]}" =~ ^[0-9a-fA-F]{64}$ ) ]]; then + unset access + echo -e "${idsCL[LightRed]}The authorized package metadata is incomplete.${idsCL[Default]}" + return 1 + fi + + TAPM_SESSION_TOKEN="${access[0]}" + TAPM_PACKAGE_URL="${access[1]}" + TAPM_PACKAGE_SHA256="${access[2],,}" + TAPM_PACKAGE_VERSION="${access[3]}" + unset access + return 0 +} + INSTALL_PULSE() { echo bash <(curl -fsSL https://github.com/rcourtman/Pulse/releases/latest/download/install.sh) @@ -28,6 +113,11 @@ INSTALL_ACRONIS() { [Nn]) echo;; * ) echo + if ! TAPM_AUTHORIZE "install-acronis" "" "Acronis installation"; then + FINISH_ACTION + return + fi + TAPM_CLEAR_AUTHORIZATION cd /tmp || return 1 wget "https://us5-cloud.acronis.com/bc/api/ams/links/agents/redirect?language=multi&channel=CURRENT&system=linux&architecture=64&productType=enterprise&login=010180ae-63c4-4495-bed0-4ec934c25af9&white_labeled=0" -O ./acronisinstall chmod +x ./acronisinstall @@ -81,6 +171,11 @@ INSTALL_SCREENCONNECT() { [Nn]) echo;; * ) echo + if ! TAPM_AUTHORIZE "install-screenconnect" "" "ScreenConnect installation"; then + FINISH_ACTION + return + fi + TAPM_CLEAR_AUTHORIZATION echo -en "\n${idsCL[LightYellow]}Paste the URL provided from the Build Installer: ${idsCL[Default]}" read -r -s SCURL echo @@ -104,6 +199,11 @@ INSTALL_RMM() { [Nn]) echo;; * ) echo + if ! TAPM_AUTHORIZE "install-rmm" "" "RMM installation"; then + FINISH_ACTION + return + fi + TAPM_CLEAR_AUTHORIZATION echo -en "\n${idsCL[LightYellow]}Paste the Linux Server URL provided from the Download Agent screen: ${idsCL[Default]}" read -r -s RMMURL @@ -131,81 +231,29 @@ INSTALL_RMM() { } INSTALL_S1() { - local deploycode exchange_response host_fingerprint - local -a s1_access - - echo - if ! command -v python3 >/dev/null 2>&1; then - echo -e "${idsCL[LightRed]}Python 3 is required to request the protected installer.${idsCL[Default]}" - FINISH_ACTION - return - fi - echo -en "${idsCL[LightYellow]}Paste the TAPM deployment code: ${idsCL[Default]}" - read -r -s deploycode - echo - deploycode="${deploycode^^}" - if [[ ! "$deploycode" =~ ^TAPM-[0-9A-HJKMNP-TV-Z]{5}-[0-9A-HJKMNP-TV-Z]{5}$ ]]; then - unset deploycode - echo -e "${idsCL[LightRed]}The TAPM deployment code is not valid.${idsCL[Default]}" - FINISH_ACTION - return - fi - - host_fingerprint="$(sha256sum /etc/machine-id | cut -d' ' -f1)" - exchange_response="$( - TAPM_CODE="$deploycode" TAPM_FINGERPRINT="$host_fingerprint" TAPM_HOSTNAME="$(hostname)" \ - python3 -c 'import json, os, sys; json.dump({"code": os.environ["TAPM_CODE"], "host_fingerprint": os.environ["TAPM_FINGERPRINT"], "hostname": os.environ["TAPM_HOSTNAME"]}, sys.stdout)' | - curl --fail --silent --show-error \ - --header 'Content-Type: application/json' \ - --data-binary @- "${S1_BROKER_URL}/api/v1/exchange" - )" || { - unset deploycode host_fingerprint exchange_response - echo -e "${idsCL[LightRed]}The protected SentinelOne installer could not be authorized.${idsCL[Default]}" - FINISH_ACTION - return - } - unset deploycode host_fingerprint - - mapfile -t s1_access < <( - printf '%s' "$exchange_response" | - S1_SLUG="$S1_BROKER_PACKAGE" python3 -c ' -import json, os, sys -data = json.load(sys.stdin) -package = next((item for item in data.get("packages", []) if item.get("slug") == os.environ["S1_SLUG"]), None) -if not package: - raise SystemExit(1) -print(data.get("session_token", "")) -print(package.get("download_url", "")) -print(package.get("sha256", "")) -' - ) || true - unset exchange_response - if [[ ${#s1_access[@]} -ne 3 || -z "${s1_access[0]}" || - ! "${s1_access[2]}" =~ ^[0-9a-fA-F]{64}$ ]]; then - unset s1_access - echo -e "${idsCL[LightRed]}SentinelOne is not included in this deployment authorization.${idsCL[Default]}" + if ! TAPM_AUTHORIZE "" "$S1_BROKER_PACKAGE" "SentinelOne installation"; then FINISH_ACTION return fi rm -f "/tmp/${S1_PACKAGE}" - if ! printf 'header = "Authorization: Bearer %s"\n' "${s1_access[0]}" | + if ! printf 'header = "Authorization: Bearer %s"\n' "$TAPM_SESSION_TOKEN" | curl --fail --location --show-error --config - \ - --output "/tmp/${S1_PACKAGE}" "${s1_access[1]}"; then - unset s1_access + --output "/tmp/${S1_PACKAGE}" "$TAPM_PACKAGE_URL"; then + TAPM_CLEAR_AUTHORIZATION rm -f "/tmp/${S1_PACKAGE}" echo -e "${idsCL[LightRed]}The SentinelOne installer download failed.${idsCL[Default]}" FINISH_ACTION return fi - if [[ "$(sha256sum "/tmp/${S1_PACKAGE}" | cut -d' ' -f1)" != "${s1_access[2],,}" ]]; then - unset s1_access + if [[ "$(sha256sum "/tmp/${S1_PACKAGE}" | cut -d' ' -f1)" != "$TAPM_PACKAGE_SHA256" ]]; then + TAPM_CLEAR_AUTHORIZATION rm -f "/tmp/${S1_PACKAGE}" echo -e "${idsCL[LightRed]}The SentinelOne installer checksum did not match. The file was removed.${idsCL[Default]}" FINISH_ACTION return fi - unset s1_access + TAPM_CLEAR_AUTHORIZATION echo -en "${idsCL[LightYellow]}Paste the customers SentinelOne Site Token: ${idsCL[Default]}" read -r -s s1token @@ -773,7 +821,7 @@ MONITORING_MENU() { dpkg-query -W -f='${Status}' sentinelagent 2>/dev/null | grep -q "install ok installed" && labels+=("SentinelOne agent (installed)") || - labels+=("Install SentinelOne agent v${S1_VERSION//_/.}") + labels+=("Install SentinelOne agent") systemctl is-active --quiet 'connectwise*' && labels+=("ScreenConnect agent (installed)") ||