Files
TA-ProxMenu/proxmenu-scripts.sh
T
2026-07-25 14:51:42 -05:00

1118 lines
33 KiB
Bash
Executable File

#!/usr/bin/env bash
# TA-Proxmenu - Proxmox Setup Scripts for TA Use
[ "${2:-}" != "q" ] && source /opt/idssys/defaults/colors.inc
source /opt/idssys/defaults/default.inc
source /opt/idssys/ta-proxmenu/defaults.inc
ACTION_REQUESTED=0
[[ -n "${action:-}" ]] && ACTION_REQUESTED=1
FINISH_ACTION() {
(( ACTION_REQUESTED == 1 )) && exit 0
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)
echo
echo -e "\n${idsCL[Green]}Pulse has been installed${idsCL[Default]}"
FINISH_ACTION
}
INSTALL_ACRONIS() {
read -n 1 -p "Are you sure you wish to install Acronis (Y/n)?" choice
case "$choice" in
[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
./acronisinstall
rm -f ./acronisinstall
echo
echo -e "\n${idsCL[Green]}Acronis has been installed${idsCL[Default]}"
FINISH_ACTION
;;
esac
}
INSTALL_PROXMENUX() {
# read -n 1 -p "Are you sure you wish to install ProxMenux (Y/n)?" choice
# case "$choice" in
# [Nn]) MAIN_MENU;;
# * )
# echo
bash -c "$(wget -qLO - https://raw.githubusercontent.com/MacRimi/ProxMenux/main/install_proxmenux.sh)"
# systemctl disable --now proxmenux-monitor
menu
# echo -e "\n${idsCL[Green]}ProxMenux has been installed${idsCL[Default]}"
# esac
}
PROXMENUX_POST_INSTALL() {
PMFLDR='/usr/local/share/proxmenux/scripts/post_install'
[ ! -f "${PMFLDR}/customizable_post_install.sh" ] && INSTALL_PROXMENUX
bash "${PMFLDR}/customizable_post_install.sh"
touch /opt/.PROXMENUX_POST_INSTALL
[ -s /etc/apt/sources.list ] && cat /dev/null > /etc/apt/sources.list
}
INSTALL_GLANCES() {
read -n 1 -p "Are you sure you wish to install Glances (Y/n)?" choice
case "$choice" in
[Nn]) echo;;
* )
echo
apt install glances -y
echo -e "\n${idsCL[Green]}Glances has been installed${idsCL[Default]}"
FINISH_ACTION
esac
}
INSTALL_SCREENCONNECT() {
read -n 1 -p "Are you sure you wish to install ScreenConnect (Y/n)?" choice
case "$choice" in
[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
[[ -n "$SCURL" ]] || { echo "No URL supplied."; FINISH_ACTION; return; }
wget "${SCURL}" -O /tmp/scinstall
unset SCURL
dpkg -i /tmp/scinstall
apt install --fix-broken -y
apt remove "connectwis*" -y > /dev/null 2>&1
dpkg -i /tmp/scinstall
rm -f /tmp/scinstall
systemctl disable --now proxmenux-monitor
echo -e "\n${idsCL[Green]}ScreenConnect has been installed${idsCL[Default]}"
FINISH_ACTION
esac
}
INSTALL_RMM() {
read -n 1 -p "Are you sure you wish to install RMM (Y/n)?" choice
case "$choice" in
[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
echo
[[ -n "$RMMURL" ]] || { echo "No URL supplied."; FINISH_ACTION; return; }
wget "${RMMURL}" -O /tmp/rmminstall
if [[ "$RMMURL" != *TKN* || "$RMMURL" != */RUN* ]]; then
echo "Unable to extract the RMM token from the URL."
unset RMMURL
FINISH_ACTION
return
fi
TOKEN="${RMMURL#*TKN}"
TOKEN="${TOKEN%%/RUN*}"
unset RMMURL
[[ -n "$TOKEN" ]] || { echo "The RMM token is empty."; FINISH_ACTION; return; }
TOKEN="$TOKEN" bash /tmp/rmminstall
unset TOKEN
systemctl restart ITSPlatform
# rm -f /tmp/rmminstall
echo -e "\n${idsCL[Green]}RMM has been installed${idsCL[Default]}"
FINISH_ACTION
esac
}
INSTALL_S1() {
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' "$TAPM_SESSION_TOKEN" |
curl --fail --location --show-error --config - \
--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)" != "$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
TAPM_CLEAR_AUTHORIZATION
echo -en "${idsCL[LightYellow]}Paste the customers SentinelOne Site Token: ${idsCL[Default]}"
read -r -s s1token
echo
[[ -n "$s1token" ]] || {
rm -f "/tmp/${S1_PACKAGE}"
echo "No SentinelOne site token supplied."
FINISH_ACTION
return
}
cd /tmp || return 1
if ! dpkg -i "/tmp/${S1_PACKAGE}"; then
unset s1token
rm -f "/tmp/${S1_PACKAGE}"
echo -e "${idsCL[LightRed]}SentinelOne installation failed.${idsCL[Default]}"
FINISH_ACTION
return
fi
/opt/sentinelone/bin/sentinelctl management token set "$s1token"
unset s1token
/opt/sentinelone/bin/sentinelctl control start
rm -f "/tmp/${S1_PACKAGE}"
echo -e "\n${idsCL[Green]}SentinelOne Agent has been installed. Make sure its added to a \"DETECT ONLY\" policy${idsCL[Default]}"
FINISH_ACTION
}
INSTALL_OMSA() {
read -n 1 -p "Are you sure you wish to install Dell OpenManage Administrator (Y/n)?" choice
case "$choice" in
[Nn]) echo;;
* )
echo
mkdir -p /tmp/omsa
cd /tmp/omsa || return 1
apt install -y gnupg libcurl4t64 libncurses6 libxslt1.1 libgpm2 libtinfo6
mkdir -p /etc/apt/keyrings
wget -qO - https://linux.dell.com/repo/pgp_pubkeys/0x1285491434D8786F.asc | gpg --dearmor -o /etc/apt/keyrings/linux.dell.com.gpg
chmod +r /etc/apt/keyrings/linux.dell.com.gpg
echo "deb [signed-by=/etc/apt/keyrings/linux.dell.com.gpg] http://linux.dell.com/repo/community/openmanage/11000/jammy jammy main" > /etc/apt/sources.list.d/linux.dell.com.list
apt update
wget -c http://archive.ubuntu.com/ubuntu/pool/universe/o/openwsman/libwsman-curl-client-transport1_2.6.5-0ubuntu16_amd64.deb
wget -c http://archive.ubuntu.com/ubuntu/pool/universe/o/openwsman/libwsman-client4t64_2.6.5-0ubuntu16_amd64.deb
wget -c http://archive.ubuntu.com/ubuntu/pool/universe/o/openwsman/libwsman1t64_2.6.5-0ubuntu16_amd64.deb
# wget -c http://http.us.debian.org/debian/pool/main/libx/libxml2/libxml2-16_2.15.1+dfsg-2+b1_amd64.deb
wget -c http://http.us.debian.org/debian/pool/main/libx/libxml2/libxml2-16_2.15.2+dfsg-0.1_amd64.deb
wget -c http://archive.ubuntu.com/ubuntu/pool/universe/o/openwsman/libwsman-server1t64_2.6.5-0ubuntu16_amd64.deb
wget -c http://archive.ubuntu.com/ubuntu/pool/universe/s/sblim-sfcc/libcimcclient0_2.2.8-0ubuntu2_amd64.deb
wget -c http://archive.ubuntu.com/ubuntu/pool/universe/o/openwsman/openwsman_2.6.5-0ubuntu16_amd64.deb
wget -c http://archive.ubuntu.com/ubuntu/pool/multiverse/c/cim-schema/cim-schema_2.48.0-0ubuntu1_all.deb
wget -c http://archive.ubuntu.com/ubuntu/pool/universe/s/sblim-sfc-common/libsfcutil0_1.0.1-0ubuntu4_amd64.deb
wget -c http://archive.ubuntu.com/ubuntu/pool/multiverse/s/sblim-sfcb/sfcb_1.4.9-0ubuntu7_amd64.deb
wget -c http://archive.ubuntu.com/ubuntu/pool/universe/s/sblim-cmpi-devel/libcmpicppimpl0_2.0.3-0ubuntu2_amd64.deb
wget -c http://ftp.us.debian.org/debian/pool/main/o/openssl/libssl1.1_1.1.1w-0+deb11u1_amd64.deb
dpkg -i libwsman-curl-client-transport1_2.6.5-0ubuntu16_amd64.deb
dpkg -i libwsman-client4t64_2.6.5-0ubuntu16_amd64.deb
dpkg -i libxml2-16_2.15.2+dfsg-0.1_amd64.deb
dpkg -i libwsman1t64_2.6.5-0ubuntu16_amd64.deb
dpkg -i libwsman-server1t64_2.6.5-0ubuntu16_amd64.deb
dpkg -i libcimcclient0_2.2.8-0ubuntu2_amd64.deb
dpkg -i openwsman_2.6.5-0ubuntu16_amd64.deb
dpkg -i cim-schema_2.48.0-0ubuntu1_all.deb
dpkg -i libsfcutil0_1.0.1-0ubuntu4_amd64.deb
dpkg -i sfcb_1.4.9-0ubuntu7_amd64.deb
dpkg -i libcmpicppimpl0_2.0.3-0ubuntu2_amd64.deb
dpkg -i libssl1.1_1.1.1w-0+deb11u1_amd64.deb
apt install -y srvadmin-all
/opt/dell/srvadmin/sbin/srvadmin-services.sh start
rm -rf -- /tmp/omsa
echo -e "\n${idsCL[Green]}Dell OMSA has been installed${idsCL[Default]}"
echo -e "\n${idsCL[LightCyan]}Available at: ${idsCL[LightGreen]}https://${RNIP}:1311${idsCL[Default]}"
FINISH_ACTION
esac
}
DOWNLOAD_VIRTIO() {
echo -e "\n${idsCL[LightCyan]}Current \"Stable\" version available for download: ${idsCL[White]}${VIRTIO_FILE}${idsCL[Default]}"
if [ -f "${DLDIR}/${VIRTIO_FILE}" ]; then
echo -en "\n${idsCL[LightRed]}Removing existing download ... "
rm -f "${DLDIR}/${VIRTIO_FILE}"
echo -e "${idsCL[Red]}Done${idsCL[Default]}"
fi
wget -q -P "$DLDIR" "$VIRTIO_DOWNLOAD_URL" &
echo -e "\n${idsCL[LightCyan]}Downloading will continue in the background\n"
FINISH_ACTION
}
DETECT_CPU(){
# if [ ! -f /etc/apt/sources.list.d/proxlb.list ]; then
# echo "deb https://repo.gyptazy.com/stable /" > /etc/apt/sources.list.d/proxlb.list
# wget -O /etc/apt/trusted.gpg.d/proxlb.asc https://repo.gyptazy.com/repository.gpg
# apt-get update
# fi
if [ ! -f /etc/apt/sources.list.d/gyptazy.list ]; then
curl https://git.gyptazy.com/api/packages/gyptazy/debian/repository.key -o /etc/apt/keyrings/gyptazy.asc
echo "deb [signed-by=/etc/apt/keyrings/gyptazy.asc] https://packages.gyptazy.com/api/packages/gyptazy/debian trixie main" | sudo tee -a /etc/apt/sources.list.d/gyptazy.list
apt update
fi
if [ "$(dpkg -l | awk '/proxclmc/ {print }'|wc -l)" -eq 0 ]; then
apt -y install proxclmc
fi
echo
proxclmc
echo
echo -en "${idsCL[LightCyan]}Would you like to set '${idsCL[LightGreen]}cpu: $(proxclmc --list-only)${idsCL[LightCyan]}' on all VMs (y/N)?${idsCL[Default]} "
read -n 1 choice
case "$choice" in
[Yy])
sed -i "/cpu:/c cpu: $(proxclmc --list-only)" /etc/pve/nodes/*/qemu-server/*.conf
echo
echo -e "\n${idsCL[Green]}All VM's have been reconfigured\n${idsCL[LightCyan]}This will require the VM's to be powered off and then turned back on in order to take effect${idsCL[Default]}"
FINISH_ACTION
;;
*) echo;;
esac
}
RESTART_SERVICE_GROUP() {
local description="$1"
shift
local -a services=("$@")
local service
local failed=0
local choice
if (( ${RESTART_ASSUME_YES:-0} == 1 )); then
choice="y"
else
echo -en "${idsCL[LightCyan]}Restart ${description} on this host (Y/n)?${idsCL[Default]} "
read -n 1 choice
echo
fi
[[ "$choice" =~ ^[Nn]$ ]] && return
echo -e "\n${idsCL[Yellow]}Restarting ${description}...${idsCL[Default]}"
if ! systemctl restart "${services[@]}"; then
failed=1
fi
echo
for service in "${services[@]}"; do
if systemctl is-active --quiet "$service"; then
echo -e " ${idsCL[Green]}[active]${idsCL[Default]} ${service}"
else
echo -e " ${idsCL[Red]}[failed]${idsCL[Default]} ${service}"
failed=1
fi
done
if (( failed == 0 )); then
echo -e "\n${idsCL[Green]}Service restart completed successfully.${idsCL[Default]}"
else
echo -e "\n${idsCL[Red]}One or more services failed to restart.${idsCL[Default]}"
echo "Review: journalctl -u <service> --since '-10 minutes'"
fi
FINISH_ACTION
}
RESTART_CLUSTER_FILESYSTEM() {
local choice
echo -e "${idsCL[LightYellow]}This temporarily interrupts /etc/pve (pmxcfs) and cluster configuration access.${idsCL[Default]}"
echo -e "${idsCL[LightYellow]}It does not restart Corosync or the HA daemons.${idsCL[Default]}"
if systemctl is-active --quiet corosync &&
! timeout 5 pvecm status 2>/dev/null |
grep -Eq '^Quorate:[[:space:]]+Yes[[:space:]]*$'; then
echo -e "\n${idsCL[Red]}The cluster is not quorate. pve-cluster will not be restarted.${idsCL[Default]}"
ENTER2CONTINUE
return
fi
echo -en "\n${idsCL[LightCyan]}Restart pve-cluster on this host (y/N)?${idsCL[Default]} "
read -n 1 choice
echo
[[ "$choice" =~ ^[Yy]$ ]] || return
if systemctl restart pve-cluster &&
systemctl is-active --quiet pve-cluster &&
timeout 15 bash -c 'until test -d /etc/pve/nodes; do sleep 1; done'; then
echo -e "\n${idsCL[Green]}pve-cluster restarted and /etc/pve is available.${idsCL[Default]}"
else
echo -e "\n${idsCL[Red]}pve-cluster did not recover normally.${idsCL[Default]}"
echo "Review: journalctl -u pve-cluster --since '-10 minutes'"
fi
ENTER2CONTINUE
}
RESTART_PVE_SERVICES() {
local requested_choice="${1:-}"
local RESTART_ASSUME_YES=0
[[ "$requested_choice" =~ ^[Nn]$ ]] && return
[[ "$requested_choice" =~ ^[Yy]$ ]] && RESTART_ASSUME_YES=1
RESTART_SERVICE_GROUP "core Proxmox management services" \
pvedaemon pveproxy pvestatd pvescheduler
}
MAINTENANCE_MODE(){
local local_node
local_node="$(hostname -s)"
if ha-manager status | grep -F "$local_node" | grep -q "maintenance mode"; then
echo -en "${idsCL[LightCyan]}Take the local host out of maintenance mode (Y/n)?${idsCL[Default]} "
else
echo -en "${idsCL[LightCyan]}Put the local host into maintenance mode (Y/n)?${idsCL[Default]} "
fi
read -n 1 choice
case "$choice" in
[Nn]) echo;;
*) echo
if ha-manager status | grep -F "$local_node" | grep -q "maintenance mode"; then
ha-manager crm-command node-maintenance disable "$local_node"
echo -e "\n${idsCL[Green]}This host will be taken out of maintenance mode${idsCL[Default]}\n"
else
ha-manager crm-command node-maintenance enable "$local_node"
echo -e "\n${idsCL[Green]}This host will be entered into maintenance mode${idsCL[Default]}\n"
bash /opt/idssys/ta-proxmenu/inc/evacuate-proxmox-node.sh
fi
FINISH_ACTION
;;
esac
}
INSTALL_KEEPALIVE() {
read -n 1 -p "Are you sure you wish to install Keepalive on all Hosts (Y/n)?" choice
case "$choice" in
[Nn]) echo;;
* )
echo
bash /opt/idssys/ta-proxmenu/inc/deploy-proxmox-keepalived.sh
echo -e "\n${idsCL[Green]}Keepalive has been installed${idsCL[Default]}"
FINISH_ACTION
esac
}
UPDATE_CACHE_DIR='/var/cache/ta-proxmenu'
UPDATE_CACHE_FILE="${UPDATE_CACHE_DIR}/update-status"
UPDATE_CACHE_SECONDS=14400
UPDATE_CHECK_PID=''
UPDATE_STATUS='unknown'
UPDATE_REMOTE_COMMIT=''
WRITE_UPDATE_CACHE() {
local status="$1"
local branch="$2"
local local_commit="$3"
local remote_commit="$4"
local cache_temp
mkdir -p "$UPDATE_CACHE_DIR" || return 1
cache_temp="$(mktemp "${UPDATE_CACHE_DIR}/.update-status.XXXXXX")" || return 1
printf '%s|%s|%s|%s|%s\n' \
"$(date +%s)" "$branch" "$local_commit" "$remote_commit" "$status" \
>"$cache_temp"
chmod 0644 "$cache_temp"
mv -f "$cache_temp" "$UPDATE_CACHE_FILE"
}
UPDATE_CHECK_WORKER() {
local branch
local local_commit
local remote_commit
local status
branch="$(git -C "$FOLDER" branch --show-current 2>/dev/null)"
local_commit="$(git -C "$FOLDER" rev-parse HEAD 2>/dev/null)"
if [[ -z "$branch" || -z "$local_commit" ]]; then
WRITE_UPDATE_CACHE "unavailable" "$branch" "$local_commit" ""
return
fi
remote_commit="$(git -C "$FOLDER" ls-remote origin "refs/heads/${branch}" 2>/dev/null | cut -f1)"
if [[ -z "$remote_commit" ]]; then
status="unavailable"
elif [[ "$local_commit" == "$remote_commit" ]]; then
status="current"
else
status="available"
fi
WRITE_UPDATE_CACHE "$status" "$branch" "$local_commit" "$remote_commit"
}
START_UPDATE_CHECK() {
if [[ -n "$UPDATE_CHECK_PID" ]] &&
kill -0 "$UPDATE_CHECK_PID" 2>/dev/null; then
return
fi
UPDATE_CHECK_WORKER >/dev/null 2>&1 &
UPDATE_CHECK_PID="$!"
}
LOAD_UPDATE_STATUS() {
local checked_at
local cached_branch
local cached_local
local cached_remote
local cached_status
local current_branch
local current_commit
local now
UPDATE_STATUS='unknown'
UPDATE_REMOTE_COMMIT=''
current_branch="$(git -C "$FOLDER" branch --show-current 2>/dev/null)"
current_commit="$(git -C "$FOLDER" rev-parse HEAD 2>/dev/null)"
now="$(date +%s)"
if [[ -r "$UPDATE_CACHE_FILE" ]]; then
IFS='|' read -r checked_at cached_branch cached_local cached_remote cached_status \
<"$UPDATE_CACHE_FILE"
if [[ "$checked_at" =~ ^[0-9]+$ ]] &&
(( now - checked_at < UPDATE_CACHE_SECONDS )) &&
[[ "$cached_branch" == "$current_branch" ]] &&
[[ "$cached_local" == "$current_commit" ]] &&
[[ "$cached_status" =~ ^(current|available|unavailable)$ ]]; then
UPDATE_STATUS="$cached_status"
UPDATE_REMOTE_COMMIT="$cached_remote"
return
fi
fi
UPDATE_STATUS='checking'
START_UPDATE_CHECK
}
FORCE_UPDATE_CHECK() {
local attempts=0
rm -f "$UPDATE_CACHE_FILE"
UPDATE_CHECK_PID=''
START_UPDATE_CHECK
echo -en "${idsCL[LightCyan]}Checking current branch for updates"
while (( attempts < 20 )); do
sleep 0.5
LOAD_UPDATE_STATUS
[[ "$UPDATE_STATUS" != "checking" ]] && break
echo -n "."
((attempts++))
done
echo -e "${idsCL[Default]}"
case "$UPDATE_STATUS" in
available)
echo -e "${idsCL[LightYellow]}An update is available for the current branch.${idsCL[Default]}"
;;
current)
echo -e "${idsCL[Green]}TA-ProxMenu is current.${idsCL[Default]}"
;;
*)
echo -e "${idsCL[Red]}The update status could not be determined.${idsCL[Default]}"
;;
esac
ENTER2CONTINUE
}
MENU_HEADER() {
local version_display
LOAD_UPDATE_STATUS
case "$UPDATE_STATUS" in
available)
version_display="${idsCL[LightYellow]}${VERS} ** UPDATE AVAILABLE **${idsCL[Default]}"
;;
checking)
version_display="${idsCL[LightCyan]}${VERS} (checking for updates)${idsCL[Default]}"
;;
*)
version_display="${idsCL[White]}${VERS}${idsCL[Default]}"
;;
esac
clear
echo
echo -e " ${idsCL[Green]}TA-ProxMenu - Proxmox Setup Scripts${idsCL[Default]} ${version_display}"
echo -e "${idsCL[Green]}---------------------------------------------------------------------------${idsCL[Default]}"
echo -e " Hostname: ${idsCL[Cyan]}$(hostname -s)${idsCL[Default]}"
echo -e " IP Address: ${idsCL[Cyan]}${RNIP:-Unavailable}${idsCL[Default]}"
echo -e "${idsCL[Green]}---------------------------------------------------------------------------${idsCL[Default]}"
}
SELECT_MENU() {
local title="$1"
local labels_name="$2"
local values_name="$3"
local allow_back="${4:-1}"
local -n labels_ref="$labels_name"
local -n values_ref="$values_name"
local selected=0
local key
local sequence
local index
while true; do
MENU_HEADER
echo
echo -e " ${idsCL[LightCyan]}${title}${idsCL[Default]}"
echo
for index in "${!labels_ref[@]}"; do
if (( index == selected )); then
printf '\e[7m %d %-64s\e[0m\n' "$((index + 1))" "${labels_ref[$index]}"
else
printf ' %d %s\n' "$((index + 1))" "${labels_ref[$index]}"
fi
done
echo
if (( allow_back == 1 )); then
echo " ↑/↓ Navigate Enter Select Number Quick Select ←/Esc/B Back Q Quit"
else
echo " ↑/↓ Navigate Enter Select Number Quick Select Q Quit"
fi
key=""
if [[ "$UPDATE_STATUS" == "checking" ]]; then
IFS= read -rsn1 -t 1 key || continue
else
IFS= read -rsn1 key
fi
case "$key" in
"")
MENU_SELECTION="${values_ref[$selected]}"
return 0
;;
[1-9])
index=$((10#$key - 1))
if (( index < ${#values_ref[@]} )); then
MENU_SELECTION="${values_ref[$index]}"
return 0
fi
;;
[Qq])
MENU_SELECTION="quit"
return 0
;;
[Bb])
if (( allow_back == 1 )); then
MENU_SELECTION="back"
return 0
fi
;;
$'\e')
sequence=""
IFS= read -rsn2 -t 0.1 sequence || true
case "$sequence" in
"[A"|"OA")
(( selected = (selected - 1 + ${#labels_ref[@]}) % ${#labels_ref[@]} ))
;;
"[B"|"OB")
(( selected = (selected + 1) % ${#labels_ref[@]} ))
;;
"[C"|"OC")
MENU_SELECTION="${values_ref[$selected]}"
return 0
;;
"[D"|"OD"|"")
if (( allow_back == 1 )); then
MENU_SELECTION="back"
return 0
fi
;;
"[H")
selected=0
;;
"[F")
selected=$((${#labels_ref[@]} - 1))
;;
esac
;;
esac
done
}
SHOW_ABOUT() {
MENU_HEADER
echo
echo -e " ${idsCL[LightCyan]}About TA-ProxMenu${idsCL[Default]}"
echo
echo " Version: ${VERS}"
echo " Install: /opt/idssys/ta-proxmenu"
echo " Branch: $(git -C /opt/idssys/ta-proxmenu branch --show-current 2>/dev/null || echo unknown)"
echo
read -r -p " Press ENTER to return..." _
}
HOST_SETUP_MENU() {
local -a labels
local -a values=(
"post_install"
"cpu"
"virtio"
"glances"
"omsa"
)
while true; do
labels=("Run ProxMenux post-install configuration")
[ -f /opt/.PROXMENUX_POST_INSTALL ] &&
labels[0]="Run ProxMenux post-install configuration (previously run)"
labels+=("Detect CPU model for live migrations")
if [ -f "${DLDIR}/${VIRTIO_FILE}" ]; then
labels+=("VirtIO drivers (${VIRTIO_FILE} already downloaded)")
elif compgen -G "${DLDIR}/virtio*.iso" >/dev/null; then
labels+=("Download updated VirtIO drivers")
else
labels+=("Download current VirtIO drivers")
fi
command -v glances >/dev/null 2>&1 &&
labels+=("Glances CLI monitor (installed)") ||
labels+=("Install Glances CLI monitor")
dpkg-query -W -f='${Status}' srvadmin-all 2>/dev/null | grep -q "install ok installed" &&
labels+=("Dell OpenManage Server Administrator (installed)") ||
labels+=("Install Dell OpenManage Server Administrator")
SELECT_MENU "Host Setup" labels values
case "$MENU_SELECTION" in
post_install) PROXMENUX_POST_INSTALL;;
cpu) DETECT_CPU;;
virtio) DOWNLOAD_VIRTIO;;
glances) INSTALL_GLANCES;;
omsa) INSTALL_OMSA;;
back) return;;
quit) EXIT1; exit 0;;
esac
done
}
MONITORING_MENU() {
local -a labels
local -a values=("pulse" "rmm" "acronis" "sentinelone" "screenconnect")
local cluster_resources
while true; do
cluster_resources="$(pvesh get /cluster/resources 2>/dev/null)"
grep -qi pulse <<< "$cluster_resources" &&
labels=("Pulse monitoring (installed)") ||
labels=("Install Pulse monitoring")
systemctl is-active --quiet ITSPlatform &&
labels+=("ConnectWise RMM agent (installed)") ||
labels+=("Install ConnectWise RMM agent")
dpkg-query -W -f='${Status}' cyberprotect 2>/dev/null | grep -q "install ok installed" &&
labels+=("Acronis backup agent (installed)") ||
labels+=("Install Acronis backup agent")
dpkg-query -W -f='${Status}' sentinelagent 2>/dev/null | grep -q "install ok installed" &&
labels+=("SentinelOne agent (installed)") ||
labels+=("Install SentinelOne agent")
systemctl is-active --quiet 'connectwise*' &&
labels+=("ScreenConnect agent (installed)") ||
labels+=("Install ScreenConnect agent")
SELECT_MENU "Monitoring & Agents" labels values
case "$MENU_SELECTION" in
pulse) INSTALL_PULSE;;
rmm) INSTALL_RMM;;
acronis) INSTALL_ACRONIS;;
sentinelone) INSTALL_S1;;
screenconnect) INSTALL_SCREENCONNECT;;
back) return;;
quit) EXIT1; exit 0;;
esac
done
}
CLUSTER_MENU() {
local -a labels
local -a values=("maintenance" "services" "keepalived")
while true; do
if ha-manager status | grep -F "$(hostname -s)" | grep -q "maintenance mode"; then
labels=("Take this host out of maintenance mode")
else
labels=("Put this host into maintenance mode and evacuate guests")
fi
labels+=("Proxmox service recovery")
dpkg-query -W -f='${Status}' keepalived 2>/dev/null | grep -q "install ok installed" &&
labels+=("Deploy/reconfigure Keepalived (installed locally)") ||
labels+=("Deploy Keepalived on all cluster hosts")
SELECT_MENU "Cluster & Maintenance" labels values
case "$MENU_SELECTION" in
maintenance) MAINTENANCE_MODE;;
services) SERVICE_RECOVERY_MENU;;
keepalived) INSTALL_KEEPALIVE;;
back) return;;
quit) EXIT1; exit 0;;
esac
done
}
SERVICE_RECOVERY_MENU() {
local -a labels=(
"Restart Web UI and API (pveproxy, pvedaemon)"
"Restart statistics collection (pvestatd)"
"Restart task scheduler (pvescheduler)"
"Restart core management services"
"Restart cluster filesystem (pve-cluster)"
)
local -a values=("web" "statistics" "scheduler" "core" "clusterfs")
while true; do
SELECT_MENU "Proxmox Service Recovery" labels values
case "$MENU_SELECTION" in
web)
RESTART_SERVICE_GROUP "Web UI and API services" pveproxy pvedaemon
;;
statistics)
RESTART_SERVICE_GROUP "statistics collection" pvestatd
;;
scheduler)
RESTART_SERVICE_GROUP "task scheduler" pvescheduler
;;
core)
RESTART_PVE_SERVICES
;;
clusterfs)
RESTART_CLUSTER_FILESYSTEM
;;
back) return;;
quit) EXIT1; exit 0;;
esac
done
}
SWITCH_SCRIPT_BRANCH() {
local target_branch="$1"
local current_branch
local choice
current_branch="$(git -C "$FOLDER" branch --show-current 2>/dev/null)"
if [[ "$target_branch" == "$current_branch" ]]; then
echo -e "\n${idsCL[Green]}TA-ProxMenu is already using '${target_branch}'.${idsCL[Default]}"
ENTER2CONTINUE
return
fi
if [[ -n "$(git -C "$FOLDER" status --porcelain --untracked-files=normal)" ]]; then
echo -e "\n${idsCL[Red]}The installed TA-ProxMenu repository has local changes.${idsCL[Default]}"
echo "Branch switching was refused to protect those files."
echo
git -C "$FOLDER" status --short
ENTER2CONTINUE
return
fi
echo -en "\n${idsCL[LightCyan]}Switch TA-ProxMenu from '${current_branch}' to '${target_branch}' (y/N)?${idsCL[Default]} "
read -n 1 choice
echo
[[ "$choice" =~ ^[Yy]$ ]] || return
if ! timeout 30 git -C "$FOLDER" fetch origin \
"refs/heads/${target_branch}:refs/remotes/origin/${target_branch}"; then
echo -e "${idsCL[Red]}Failed to fetch '${target_branch}' from origin.${idsCL[Default]}"
ENTER2CONTINUE
return
fi
if git -C "$FOLDER" show-ref --verify --quiet "refs/heads/${target_branch}"; then
git -C "$FOLDER" switch "$target_branch" || {
ENTER2CONTINUE
return
}
else
git -C "$FOLDER" switch --create "$target_branch" \
--track "origin/${target_branch}" || {
ENTER2CONTINUE
return
}
fi
if ! git -C "$FOLDER" reset --hard "origin/${target_branch}"; then
echo -e "${idsCL[Red]}Failed to synchronize '${target_branch}' with origin.${idsCL[Default]}"
ENTER2CONTINUE
return
fi
rm -f "$UPDATE_CACHE_FILE"
echo -e "\n${idsCL[Green]}Now using TA-ProxMenu branch '${target_branch}'.${idsCL[Default]}"
sleep 1
exec /opt/idssys/ta-proxmenu/run.sh
}
BRANCH_MANAGEMENT_MENU() {
local -a labels=()
local -a values=()
local branch
local branch_version
local current_branch
MENU_HEADER
echo
echo -e " ${idsCL[LightCyan]}Refreshing remote branch list...${idsCL[Default]}"
if ! timeout 30 git -C "$FOLDER" fetch origin \
'+refs/heads/*:refs/remotes/origin/*' --prune >/dev/null 2>&1; then
echo -e "\n${idsCL[Red]}Could not retrieve branches from origin.${idsCL[Default]}"
ENTER2CONTINUE
return
fi
current_branch="$(git -C "$FOLDER" branch --show-current 2>/dev/null)"
while IFS= read -r branch; do
[[ -n "$branch" && "$branch" != "HEAD" ]] || continue
branch_version="$(
git -C "$FOLDER" show "refs/remotes/origin/${branch}:defaults.inc" \
2>/dev/null |
awk -F"'" '/^VERS=/{ print $2; exit }'
)"
if [[ "$branch" == "$current_branch" ]]; then
labels+=("${branch} (current, version ${branch_version:-unknown})")
else
labels+=("${branch} (version ${branch_version:-unknown})")
fi
values+=("branch:${branch}")
done < <(
git -C "$FOLDER" for-each-ref \
--format='%(refname:strip=3)' refs/remotes/origin |
sort -V
)
if (( ${#labels[@]} == 0 )); then
echo -e "\n${idsCL[Red]}No remote branches were found.${idsCL[Default]}"
ENTER2CONTINUE
return
fi
while true; do
SELECT_MENU "Git Branch Management" labels values
case "$MENU_SELECTION" in
branch:*)
SWITCH_SCRIPT_BRANCH "${MENU_SELECTION#branch:}"
;;
back) return;;
quit) EXIT1; exit 0;;
esac
done
}
UTILITIES_MENU() {
local -a labels
local -a values=("install_update" "check_update" "branches" "about")
local choice
while true; do
LOAD_UPDATE_STATUS
case "$UPDATE_STATUS" in
available)
labels=("Install available update")
;;
current)
labels=("TA-ProxMenu is current")
;;
checking)
labels=("Update check in progress")
;;
*)
labels=("Update status unavailable")
;;
esac
labels+=(
"Check again now"
"Git branch management"
"Version and installation information"
)
SELECT_MENU "Utilities" labels values
case "$MENU_SELECTION" in
install_update)
if [[ "$UPDATE_STATUS" != "available" ]]; then
echo -e "\n${idsCL[LightCyan]}No available update is currently detected.${idsCL[Default]}"
ENTER2CONTINUE
continue
fi
echo -en "\n${idsCL[LightCyan]}Install the update for the current branch (y/N)?${idsCL[Default]} "
read -n 1 choice
echo
if [[ "$choice" =~ ^[Yy]$ ]] &&
/opt/idssys/ta-proxmenu/run.sh update; then
exec /opt/idssys/ta-proxmenu/run.sh
fi
ENTER2CONTINUE
;;
check_update) FORCE_UPDATE_CHECK;;
branches) BRANCH_MANAGEMENT_MENU;;
about) SHOW_ABOUT;;
back) return;;
quit) EXIT1; exit 0;;
esac
done
}
MAIN_MENU() {
local -a labels=(
"Host Setup"
"Monitoring & Agents"
"Cluster & Maintenance"
"Utilities"
"Quit"
)
local -a values=("host" "monitoring" "cluster" "utilities" "quit")
while true; do
SELECT_MENU "Main Menu" labels values 0
case "$MENU_SELECTION" in
host) HOST_SETUP_MENU;;
monitoring) MONITORING_MENU;;
cluster) CLUSTER_MENU;;
utilities) UTILITIES_MENU;;
quit) EXIT1; exit 0;;
esac
done
}
if (( ACTION_REQUESTED == 1 )); then
case "$action" in
pulse) INSTALL_PULSE;;
rmm) INSTALL_RMM;;
omsa) INSTALL_OMSA;;
glances) INSTALL_GLANCES;;
acronis) INSTALL_ACRONIS;;
post-install|post_install) PROXMENUX_POST_INSTALL;;
proxmenux) [ ! -f /usr/local/bin/menu ] && INSTALL_PROXMENUX || /usr/local/bin/menu;;
virtio) DOWNLOAD_VIRTIO;;
sentinelone|s1) INSTALL_S1;;
screenconnect) INSTALL_SCREENCONNECT;;
restart) RESTART_PVE_SERVICES "${2:-}";;
cpu) DETECT_CPU;;
maintenance|mm) MAINTENANCE_MODE;;
keepalived) INSTALL_KEEPALIVE;;
*) MAIN_MENU;;
esac
else
MAIN_MENU
fi
exit 0