1843 lines
53 KiB
Bash
Executable File
1843 lines
53 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
|
|
source /opt/idssys/ta-proxmenu/inc/git-update.inc
|
|
|
|
ACTION_REQUESTED=0
|
|
[[ -n "${action:-}" ]] && ACTION_REQUESTED=1
|
|
|
|
FINISH_ACTION() {
|
|
(( ACTION_REQUESTED == 1 )) && exit 0
|
|
ENTER2CONTINUE
|
|
}
|
|
|
|
FINISH_FAILED_ACTION() {
|
|
(( ACTION_REQUESTED == 1 )) && exit 1
|
|
ENTER2CONTINUE
|
|
return 1
|
|
}
|
|
|
|
declare -a TAPM_TEMP_DIRS=()
|
|
TAPM_TEMP_DIR=''
|
|
|
|
TAPM_CLEAN_TEMP_DIR() {
|
|
local temp_dir="${1:-}"
|
|
|
|
if [[ "$temp_dir" == /tmp/ta-proxmenu-* && -d "$temp_dir" ]]; then
|
|
rm -rf -- "$temp_dir"
|
|
fi
|
|
}
|
|
|
|
TAPM_CLEAN_ALL_TEMP_DIRS() {
|
|
local temp_dir
|
|
|
|
for temp_dir in "${TAPM_TEMP_DIRS[@]}"; do
|
|
TAPM_CLEAN_TEMP_DIR "$temp_dir"
|
|
done
|
|
}
|
|
|
|
TAPM_CREATE_TEMP_DIR() {
|
|
local label="${1:-installer}"
|
|
|
|
TAPM_TEMP_DIR="$(mktemp -d "/tmp/ta-proxmenu-${label}.XXXXXX")" || {
|
|
echo -e "${idsCL[LightRed]}Unable to create a temporary installer directory.${idsCL[Default]}"
|
|
return 1
|
|
}
|
|
if ! chmod 0700 "$TAPM_TEMP_DIR"; then
|
|
TAPM_CLEAN_TEMP_DIR "$TAPM_TEMP_DIR"
|
|
echo -e "${idsCL[LightRed]}Unable to secure the temporary installer directory.${idsCL[Default]}"
|
|
return 1
|
|
fi
|
|
TAPM_TEMP_DIRS+=("$TAPM_TEMP_DIR")
|
|
}
|
|
|
|
TAPM_VALID_HTTPS_URL() {
|
|
local url="${1:-}"
|
|
|
|
[[ "$url" == https://* &&
|
|
"$url" != *$'\n'* &&
|
|
"$url" != *$'\r'* &&
|
|
"$url" != *'"'* &&
|
|
"$url" != *\\* &&
|
|
"$url" != *[[:space:]]* ]]
|
|
}
|
|
|
|
TAPM_DOWNLOAD_HTTPS() {
|
|
local url="$1"
|
|
local destination="$2"
|
|
local label="${3:-Installer}"
|
|
|
|
if ! TAPM_VALID_HTTPS_URL "$url"; then
|
|
echo -e "${idsCL[LightRed]}${label} requires a valid HTTPS URL.${idsCL[Default]}"
|
|
return 1
|
|
fi
|
|
|
|
if ! printf 'url = "%s"\n' "$url" |
|
|
curl --fail --location --silent --show-error \
|
|
--proto '=https' --proto-redir '=https' \
|
|
--output "$destination" --config -; then
|
|
echo -e "${idsCL[LightRed]}${label} download failed.${idsCL[Default]}"
|
|
return 1
|
|
fi
|
|
|
|
if [[ ! -s "$destination" ]]; then
|
|
echo -e "${idsCL[LightRed]}${label} download was empty.${idsCL[Default]}"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
TAPM_DOWNLOAD_SHA256() {
|
|
local url="$1"
|
|
local destination="$2"
|
|
local expected_sha256="${3,,}"
|
|
local label="${4:-Installer}"
|
|
local actual_sha256
|
|
|
|
TAPM_DOWNLOAD_HTTPS "$url" "$destination" "$label" || return 1
|
|
actual_sha256="$(sha256sum "$destination" | cut -d' ' -f1)"
|
|
if [[ "$actual_sha256" != "$expected_sha256" ]]; then
|
|
rm -f -- "$destination"
|
|
echo -e "${idsCL[LightRed]}${label} checksum did not match; the file was removed.${idsCL[Default]}"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
TAPM_PACKAGE_INSTALLED() {
|
|
dpkg-query -W -f='${Status}' "$1" 2>/dev/null |
|
|
grep -q '^install ok installed$'
|
|
}
|
|
|
|
TAPM_WAIT_FOR_SERVICE() {
|
|
local service="$1"
|
|
local attempts="${2:-30}"
|
|
local attempt=0
|
|
|
|
while (( attempt < attempts )); do
|
|
systemctl is-active --quiet "$service" && return 0
|
|
sleep 1
|
|
((attempt++))
|
|
done
|
|
|
|
return 1
|
|
}
|
|
|
|
TAPM_WAIT_FOR_TCP_PORT() {
|
|
local port="$1"
|
|
local attempts="${2:-30}"
|
|
local attempt=0
|
|
|
|
while (( attempt < attempts )); do
|
|
if ss -H -lnt 2>/dev/null |
|
|
awk -v port="$port" '$4 ~ (":" port "$") { found=1 } END { exit !found }'; then
|
|
return 0
|
|
fi
|
|
sleep 1
|
|
((attempt++))
|
|
done
|
|
|
|
return 1
|
|
}
|
|
|
|
trap TAPM_CLEAN_ALL_TEMP_DIRS EXIT
|
|
|
|
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 ||
|
|
! "${access[0]}" =~ ^[A-Za-z0-9._~-]+$ ]]; 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() {
|
|
local installer
|
|
local temp_dir
|
|
|
|
echo
|
|
if ! TAPM_CREATE_TEMP_DIR pulse; then
|
|
FINISH_FAILED_ACTION
|
|
return
|
|
fi
|
|
temp_dir="$TAPM_TEMP_DIR"
|
|
installer="${temp_dir}/install.sh"
|
|
|
|
if ! TAPM_DOWNLOAD_HTTPS \
|
|
'https://github.com/rcourtman/Pulse/releases/latest/download/install.sh' \
|
|
"$installer" 'Pulse installer' ||
|
|
! bash "$installer"; then
|
|
TAPM_CLEAN_TEMP_DIR "$temp_dir"
|
|
echo -e "${idsCL[LightRed]}Pulse installation failed.${idsCL[Default]}"
|
|
FINISH_FAILED_ACTION
|
|
return
|
|
fi
|
|
|
|
TAPM_CLEAN_TEMP_DIR "$temp_dir"
|
|
echo
|
|
echo -e "\n${idsCL[Green]}Pulse installer completed successfully.${idsCL[Default]}"
|
|
FINISH_ACTION
|
|
}
|
|
|
|
INSTALL_ACRONIS() {
|
|
local installer
|
|
local temp_dir
|
|
local url='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'
|
|
|
|
echo
|
|
if ! TAPM_AUTHORIZE "install-acronis" "" "Acronis installation"; then
|
|
FINISH_FAILED_ACTION
|
|
return
|
|
fi
|
|
TAPM_CLEAR_AUTHORIZATION
|
|
|
|
if ! TAPM_CREATE_TEMP_DIR acronis; then
|
|
FINISH_FAILED_ACTION
|
|
return
|
|
fi
|
|
temp_dir="$TAPM_TEMP_DIR"
|
|
installer="${temp_dir}/acronisinstall"
|
|
|
|
if ! TAPM_DOWNLOAD_HTTPS "$url" "$installer" 'Acronis installer' ||
|
|
! chmod 0700 "$installer" ||
|
|
! (cd "$temp_dir" && ./acronisinstall) ||
|
|
! TAPM_PACKAGE_INSTALLED cyberprotect; then
|
|
TAPM_CLEAN_TEMP_DIR "$temp_dir"
|
|
echo -e "${idsCL[LightRed]}Acronis installation failed or could not be verified.${idsCL[Default]}"
|
|
FINISH_FAILED_ACTION
|
|
return
|
|
fi
|
|
|
|
TAPM_CLEAN_TEMP_DIR "$temp_dir"
|
|
echo
|
|
echo -e "\n${idsCL[Green]}Acronis has been installed and verified.${idsCL[Default]}"
|
|
FINISH_ACTION
|
|
}
|
|
|
|
INSTALL_PROXMENUX() {
|
|
local installer
|
|
local temp_dir
|
|
|
|
if ! TAPM_CREATE_TEMP_DIR proxmenux; then
|
|
FINISH_FAILED_ACTION
|
|
return
|
|
fi
|
|
temp_dir="$TAPM_TEMP_DIR"
|
|
installer="${temp_dir}/install.sh"
|
|
|
|
if ! TAPM_DOWNLOAD_HTTPS \
|
|
'https://raw.githubusercontent.com/MacRimi/ProxMenux/main/install_proxmenux.sh' \
|
|
"$installer" 'ProxMenux installer' ||
|
|
! bash "$installer" ||
|
|
[[ ! -x /usr/local/bin/menu ]]; then
|
|
TAPM_CLEAN_TEMP_DIR "$temp_dir"
|
|
echo -e "${idsCL[LightRed]}ProxMenux installation failed or could not be verified.${idsCL[Default]}"
|
|
FINISH_FAILED_ACTION
|
|
return
|
|
fi
|
|
|
|
TAPM_CLEAN_TEMP_DIR "$temp_dir"
|
|
/usr/local/bin/menu
|
|
}
|
|
|
|
PROXMENUX_POST_INSTALL() {
|
|
local backup_dir='/var/backups/ta-proxmenu'
|
|
local backup_file=''
|
|
local post_install_dir='/usr/local/share/proxmenux/scripts/post_install'
|
|
local post_install_script="${post_install_dir}/customizable_post_install.sh"
|
|
local timestamp
|
|
|
|
if [[ ! -f "$post_install_script" ]]; then
|
|
INSTALL_PROXMENUX
|
|
[[ -f "$post_install_script" ]] || return 1
|
|
fi
|
|
|
|
if ! bash "$post_install_script"; then
|
|
echo -e "${idsCL[LightRed]}The ProxMenux post-install script failed. A completion marker was not created.${idsCL[Default]}"
|
|
FINISH_FAILED_ACTION
|
|
return
|
|
fi
|
|
|
|
if [[ -e /etc/apt/sources.list && ! -f /etc/apt/sources.list ]]; then
|
|
echo -e "${idsCL[LightRed]}/etc/apt/sources.list is not a regular file and was not changed.${idsCL[Default]}"
|
|
FINISH_FAILED_ACTION
|
|
return
|
|
fi
|
|
|
|
if [[ -s /etc/apt/sources.list ]]; then
|
|
timestamp="$(date +%Y%m%d-%H%M%S)"
|
|
if ! install -d -m 0700 "$backup_dir" ||
|
|
! backup_file="$(mktemp "${backup_dir}/sources.list.proxmenux.${timestamp}.XXXXXX")" ||
|
|
! install -m 0600 /etc/apt/sources.list "$backup_file"; then
|
|
[[ -n "$backup_file" ]] && rm -f -- "$backup_file"
|
|
echo -e "${idsCL[LightRed]}Could not back up /etc/apt/sources.list; it was not cleared.${idsCL[Default]}"
|
|
FINISH_FAILED_ACTION
|
|
return
|
|
fi
|
|
fi
|
|
|
|
if ! : >/etc/apt/sources.list ||
|
|
! chmod 0644 /etc/apt/sources.list; then
|
|
echo -e "${idsCL[LightRed]}Could not enforce an empty /etc/apt/sources.list.${idsCL[Default]}"
|
|
[[ -n "$backup_file" ]] &&
|
|
echo "Backup retained at: ${backup_file}"
|
|
FINISH_FAILED_ACTION
|
|
return
|
|
fi
|
|
|
|
if ! apt-get update; then
|
|
echo -e "${idsCL[LightRed]}APT repository validation failed after ProxMenux post-install.${idsCL[Default]}"
|
|
[[ -n "$backup_file" ]] &&
|
|
echo "Removed sources were retained at: ${backup_file}"
|
|
FINISH_FAILED_ACTION
|
|
return
|
|
fi
|
|
|
|
if ! touch /opt/.PROXMENUX_POST_INSTALL; then
|
|
echo -e "${idsCL[LightRed]}Post-install succeeded, but the completion marker could not be created.${idsCL[Default]}"
|
|
FINISH_FAILED_ACTION
|
|
return
|
|
fi
|
|
|
|
echo -e "\n${idsCL[Green]}ProxMenux post-install completed and APT sources were validated.${idsCL[Default]}"
|
|
[[ -n "$backup_file" ]] &&
|
|
echo "ProxMenux additions to sources.list were saved at: ${backup_file}"
|
|
FINISH_ACTION
|
|
}
|
|
|
|
INSTALL_GLANCES() {
|
|
echo
|
|
if ! DEBIAN_FRONTEND=noninteractive apt-get install glances -y ||
|
|
! TAPM_PACKAGE_INSTALLED glances; then
|
|
echo -e "\n${idsCL[LightRed]}Glances installation failed or could not be verified.${idsCL[Default]}"
|
|
FINISH_FAILED_ACTION
|
|
return
|
|
fi
|
|
echo -e "\n${idsCL[Green]}Glances has been installed and verified.${idsCL[Default]}"
|
|
FINISH_ACTION
|
|
}
|
|
|
|
INSTALL_SCREENCONNECT() {
|
|
local SCURL=''
|
|
local installer
|
|
local temp_dir
|
|
|
|
echo
|
|
if ! TAPM_AUTHORIZE "install-screenconnect" "" "ScreenConnect installation"; then
|
|
FINISH_FAILED_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_FAILED_ACTION; return; }
|
|
if ! TAPM_CREATE_TEMP_DIR screenconnect; then
|
|
unset SCURL
|
|
FINISH_FAILED_ACTION
|
|
return
|
|
fi
|
|
temp_dir="$TAPM_TEMP_DIR"
|
|
installer="${temp_dir}/screenconnect.deb"
|
|
|
|
if ! TAPM_DOWNLOAD_HTTPS "$SCURL" "$installer" 'ScreenConnect installer'; then
|
|
unset SCURL
|
|
TAPM_CLEAN_TEMP_DIR "$temp_dir"
|
|
FINISH_FAILED_ACTION
|
|
return
|
|
fi
|
|
unset SCURL
|
|
|
|
if ! dpkg -i "$installer"; then
|
|
if ! DEBIAN_FRONTEND=noninteractive apt-get install --fix-broken -y; then
|
|
TAPM_CLEAN_TEMP_DIR "$temp_dir"
|
|
echo -e "${idsCL[LightRed]}ScreenConnect dependency installation failed.${idsCL[Default]}"
|
|
FINISH_FAILED_ACTION
|
|
return
|
|
fi
|
|
fi
|
|
DEBIAN_FRONTEND=noninteractive apt-get remove 'connectwis*' -y >/dev/null 2>&1 || true
|
|
if ! dpkg -i "$installer" ||
|
|
! TAPM_WAIT_FOR_SERVICE 'connectwise*'; then
|
|
TAPM_CLEAN_TEMP_DIR "$temp_dir"
|
|
echo -e "${idsCL[LightRed]}ScreenConnect installation failed or its service is not active.${idsCL[Default]}"
|
|
FINISH_FAILED_ACTION
|
|
return
|
|
fi
|
|
|
|
TAPM_CLEAN_TEMP_DIR "$temp_dir"
|
|
systemctl disable --now proxmenux-monitor >/dev/null 2>&1 || true
|
|
echo -e "\n${idsCL[Green]}ScreenConnect has been installed and verified.${idsCL[Default]}"
|
|
FINISH_ACTION
|
|
}
|
|
|
|
INSTALL_RMM() {
|
|
local RMMURL=''
|
|
local TOKEN=''
|
|
local installer
|
|
local temp_dir
|
|
|
|
echo
|
|
if ! TAPM_AUTHORIZE "install-rmm" "" "RMM installation"; then
|
|
FINISH_FAILED_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_FAILED_ACTION; return; }
|
|
if [[ "$RMMURL" != *TKN* || "$RMMURL" != */RUN* ]]; then
|
|
echo "Unable to extract the RMM token from the URL."
|
|
unset RMMURL
|
|
FINISH_FAILED_ACTION
|
|
return
|
|
fi
|
|
TOKEN="${RMMURL#*TKN}"
|
|
TOKEN="${TOKEN%%/RUN*}"
|
|
if [[ -z "$TOKEN" ]]; then
|
|
unset RMMURL TOKEN
|
|
echo "The RMM token is empty."
|
|
FINISH_FAILED_ACTION
|
|
return
|
|
fi
|
|
|
|
if ! TAPM_CREATE_TEMP_DIR rmm; then
|
|
unset RMMURL TOKEN
|
|
FINISH_FAILED_ACTION
|
|
return
|
|
fi
|
|
temp_dir="$TAPM_TEMP_DIR"
|
|
installer="${temp_dir}/rmminstall"
|
|
if ! TAPM_DOWNLOAD_HTTPS "$RMMURL" "$installer" 'RMM installer'; then
|
|
unset RMMURL TOKEN
|
|
TAPM_CLEAN_TEMP_DIR "$temp_dir"
|
|
FINISH_FAILED_ACTION
|
|
return
|
|
fi
|
|
unset RMMURL
|
|
|
|
if ! TOKEN="$TOKEN" bash "$installer"; then
|
|
unset TOKEN
|
|
TAPM_CLEAN_TEMP_DIR "$temp_dir"
|
|
echo -e "${idsCL[LightRed]}RMM installation failed.${idsCL[Default]}"
|
|
FINISH_FAILED_ACTION
|
|
return
|
|
fi
|
|
unset TOKEN
|
|
TAPM_CLEAN_TEMP_DIR "$temp_dir"
|
|
|
|
if ! systemctl restart ITSPlatform ||
|
|
! TAPM_WAIT_FOR_SERVICE ITSPlatform; then
|
|
echo -e "${idsCL[LightRed]}RMM installed, but the ITSPlatform service is not active.${idsCL[Default]}"
|
|
FINISH_FAILED_ACTION
|
|
return
|
|
fi
|
|
|
|
echo -e "\n${idsCL[Green]}RMM has been installed and verified.${idsCL[Default]}"
|
|
FINISH_ACTION
|
|
}
|
|
|
|
INSTALL_S1() {
|
|
local installer
|
|
local s1token=''
|
|
local temp_dir
|
|
|
|
if ! TAPM_AUTHORIZE "" "$S1_BROKER_PACKAGE" "SentinelOne installation"; then
|
|
FINISH_FAILED_ACTION
|
|
return
|
|
fi
|
|
|
|
if ! TAPM_CREATE_TEMP_DIR sentinelone; then
|
|
TAPM_CLEAR_AUTHORIZATION
|
|
FINISH_FAILED_ACTION
|
|
return
|
|
fi
|
|
temp_dir="$TAPM_TEMP_DIR"
|
|
installer="${temp_dir}/${S1_PACKAGE}"
|
|
|
|
if ! TAPM_VALID_HTTPS_URL "$TAPM_PACKAGE_URL"; then
|
|
TAPM_CLEAR_AUTHORIZATION
|
|
TAPM_CLEAN_TEMP_DIR "$temp_dir"
|
|
echo -e "${idsCL[LightRed]}The authorized SentinelOne package URL is not valid HTTPS.${idsCL[Default]}"
|
|
FINISH_FAILED_ACTION
|
|
return
|
|
fi
|
|
if ! printf 'header = "Authorization: Bearer %s"\nurl = "%s"\n' \
|
|
"$TAPM_SESSION_TOKEN" "$TAPM_PACKAGE_URL" |
|
|
curl --fail --location --silent --show-error --config - \
|
|
--proto '=https' --proto-redir '=https' \
|
|
--output "$installer"; then
|
|
TAPM_CLEAR_AUTHORIZATION
|
|
TAPM_CLEAN_TEMP_DIR "$temp_dir"
|
|
echo -e "${idsCL[LightRed]}The SentinelOne installer download failed.${idsCL[Default]}"
|
|
FINISH_FAILED_ACTION
|
|
return
|
|
fi
|
|
if [[ ! -s "$installer" ||
|
|
"$(sha256sum "$installer" | cut -d' ' -f1)" != "$TAPM_PACKAGE_SHA256" ]]; then
|
|
TAPM_CLEAR_AUTHORIZATION
|
|
TAPM_CLEAN_TEMP_DIR "$temp_dir"
|
|
echo -e "${idsCL[LightRed]}The SentinelOne installer checksum did not match. The file was removed.${idsCL[Default]}"
|
|
FINISH_FAILED_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" ]] || {
|
|
TAPM_CLEAN_TEMP_DIR "$temp_dir"
|
|
echo "No SentinelOne site token supplied."
|
|
FINISH_FAILED_ACTION
|
|
return
|
|
}
|
|
if ! dpkg -i "$installer" ||
|
|
! /opt/sentinelone/bin/sentinelctl management token set "$s1token" ||
|
|
! /opt/sentinelone/bin/sentinelctl control start ||
|
|
! TAPM_PACKAGE_INSTALLED sentinelagent; then
|
|
unset s1token
|
|
TAPM_CLEAN_TEMP_DIR "$temp_dir"
|
|
echo -e "${idsCL[LightRed]}SentinelOne installation failed or could not be verified.${idsCL[Default]}"
|
|
FINISH_FAILED_ACTION
|
|
return
|
|
fi
|
|
unset s1token
|
|
TAPM_CLEAN_TEMP_DIR "$temp_dir"
|
|
|
|
echo -e "\n${idsCL[Green]}SentinelOne Agent has been installed and verified. Make sure it is added to a \"DETECT ONLY\" policy.${idsCL[Default]}"
|
|
FINISH_ACTION
|
|
}
|
|
|
|
TAPM_SYSTEM_PRODUCT_NAME() {
|
|
local product_name=''
|
|
|
|
if [[ -r /sys/class/dmi/id/product_name ]]; then
|
|
read -r product_name </sys/class/dmi/id/product_name
|
|
elif command -v dmidecode >/dev/null 2>&1; then
|
|
product_name="$(dmidecode -s system-product-name 2>/dev/null)"
|
|
fi
|
|
|
|
printf '%s\n' "$product_name"
|
|
}
|
|
|
|
TAPM_OMSA_SUPPORTED_HARDWARE() {
|
|
local product_name
|
|
|
|
product_name="$(TAPM_SYSTEM_PRODUCT_NAME)"
|
|
[[ "$product_name" =~ PowerEdge[[:space:]]+[[:alpha:]]+[0-9](3|4)[0-9][[:alnum:]-]* ]]
|
|
}
|
|
|
|
TAPM_OMSA_SUPPORTED_PLATFORM() {
|
|
local architecture
|
|
local codename
|
|
local os_version
|
|
local pve_version
|
|
|
|
architecture="$(dpkg --print-architecture 2>/dev/null)"
|
|
os_version="$(. /etc/os-release 2>/dev/null; printf '%s' "${VERSION_ID:-}")"
|
|
codename="$(. /etc/os-release 2>/dev/null; printf '%s' "${VERSION_CODENAME:-}")"
|
|
pve_version="$(pveversion 2>/dev/null | head -n 1)"
|
|
|
|
[[ "$architecture" == "amd64" &&
|
|
"$os_version" == "13" &&
|
|
"$codename" == "trixie" &&
|
|
"$pve_version" == pve-manager/9.* ]]
|
|
}
|
|
|
|
INSTALL_OMSA() {
|
|
local base_url='https://archive.ubuntu.com/ubuntu/pool'
|
|
local dell_key
|
|
local dell_keyring
|
|
local dell_source
|
|
local index
|
|
local product_name
|
|
local temp_dir
|
|
local -a filenames=(
|
|
'libwsman-curl-client-transport1_2.6.5-0ubuntu16_amd64.deb'
|
|
'libwsman-client4t64_2.6.5-0ubuntu16_amd64.deb'
|
|
'libxml2-16_2.15.2+dfsg-0.1_amd64.deb'
|
|
'libwsman1t64_2.6.5-0ubuntu16_amd64.deb'
|
|
'libwsman-server1t64_2.6.5-0ubuntu16_amd64.deb'
|
|
'libcimcclient0_2.2.8-0ubuntu2_amd64.deb'
|
|
'openwsman_2.6.5-0ubuntu16_amd64.deb'
|
|
'cim-schema_2.48.0-0ubuntu1_all.deb'
|
|
'libsfcutil0_1.0.1-0ubuntu4_amd64.deb'
|
|
'sfcb_1.4.9-0ubuntu7_amd64.deb'
|
|
'libcmpicppimpl0_2.0.3-0ubuntu2_amd64.deb'
|
|
'libssl1.1_1.1.1w-0+deb11u1_amd64.deb'
|
|
)
|
|
local -a urls=(
|
|
"${base_url}/universe/o/openwsman/${filenames[0]}"
|
|
"${base_url}/universe/o/openwsman/${filenames[1]}"
|
|
'https://snapshot.debian.org/file/32f51d914435fd29ce31af7ba17525f6276ea58d'
|
|
"${base_url}/universe/o/openwsman/${filenames[3]}"
|
|
"${base_url}/universe/o/openwsman/${filenames[4]}"
|
|
"${base_url}/universe/s/sblim-sfcc/${filenames[5]}"
|
|
"${base_url}/universe/o/openwsman/${filenames[6]}"
|
|
"${base_url}/multiverse/c/cim-schema/${filenames[7]}"
|
|
"${base_url}/universe/s/sblim-sfc-common/${filenames[8]}"
|
|
"${base_url}/multiverse/s/sblim-sfcb/${filenames[9]}"
|
|
"${base_url}/universe/s/sblim-cmpi-devel/${filenames[10]}"
|
|
"https://deb.debian.org/debian/pool/main/o/openssl/${filenames[11]}"
|
|
)
|
|
local -a checksums=(
|
|
'42fdd34722ac1304427f80c4176ee781d057f05669d485f0ee1da4d87df7488c'
|
|
'6d1855a2e8263e9a578b4c0bb7a963a6d99dc8d2ef41e287725799dcad0c6cb3'
|
|
'8571682a07f329bb462569502b57aced4866e5b95c2db3ec7e5414a5b3bbdc14'
|
|
'61b91e8f234c5f2f87b4bce3534bc2f2304d50cd2fd95f426f12fc73d80e27b4'
|
|
'5d3f948ab605b4973b399f53bd62bddd70eb01161769a0d39a811399fe7c2daf'
|
|
'14b9ac374f88bd44e57395e87faa76d99d02e242c813fe30083d5bbfafec5870'
|
|
'62b30fcf41dae0c1d841f67a46049b7dfa4dfffe314e4226a776eb134605b7fc'
|
|
'a87d16d41e81092c7ada43824a97cf79fab18c4a3722ef6f0476ad697a3d9ab7'
|
|
'ba890cf5f2359befd3da1e5763672ef8b03d5424fa4e3d1ef21c9d52884af247'
|
|
'3eb5dce0a873f8eb77174fdd5e02ac55a989f7a73bd5ef8c8aae501d225d7524'
|
|
'284acfbb6d675496046ee46e6d5ea6c70ceafa3781cf1eece04f612cbadf117c'
|
|
'aadf8b4b197335645b230c2839b4517aa444fd2e8f434e5438c48a18857988f7'
|
|
)
|
|
local -a package_paths=()
|
|
|
|
echo
|
|
product_name="$(TAPM_SYSTEM_PRODUCT_NAME)"
|
|
if ! TAPM_OMSA_SUPPORTED_HARDWARE; then
|
|
echo -e "${idsCL[LightRed]}Dell OMSA legacy installation is limited to PowerEdge x30/x40 systems.${idsCL[Default]}"
|
|
echo "Detected system: ${product_name:-Unknown}"
|
|
FINISH_FAILED_ACTION
|
|
return
|
|
fi
|
|
if ! TAPM_OMSA_SUPPORTED_PLATFORM; then
|
|
echo -e "${idsCL[LightRed]}This legacy OMSA package set requires PVE 9 on Debian 13 (Trixie), amd64.${idsCL[Default]}"
|
|
FINISH_FAILED_ACTION
|
|
return
|
|
fi
|
|
|
|
echo -e "${idsCL[LightYellow]}Installing legacy Dell OMSA 11.0 compatibility packages on ${product_name}.${idsCL[Default]}"
|
|
|
|
if ! TAPM_CREATE_TEMP_DIR omsa; then
|
|
FINISH_FAILED_ACTION
|
|
return
|
|
fi
|
|
temp_dir="$TAPM_TEMP_DIR"
|
|
dell_key="${temp_dir}/dell-openmanage.asc"
|
|
dell_keyring="${temp_dir}/linux.dell.com.gpg"
|
|
dell_source="${temp_dir}/linux.dell.com.list"
|
|
|
|
if ! TAPM_DOWNLOAD_SHA256 \
|
|
'https://linux.dell.com/repo/pgp_pubkeys/0x1285491434D8786F.asc' \
|
|
"$dell_key" \
|
|
'92f9622bf300f1fc8a4ef12d8e5efef6511b089c63c15e635cfc7429499e86d4' \
|
|
'Dell OpenManage signing key'; then
|
|
TAPM_CLEAN_TEMP_DIR "$temp_dir"
|
|
FINISH_FAILED_ACTION
|
|
return
|
|
fi
|
|
|
|
for index in "${!filenames[@]}"; do
|
|
package_paths+=("${temp_dir}/${filenames[$index]}")
|
|
if ! TAPM_DOWNLOAD_SHA256 "${urls[$index]}" "${package_paths[$index]}" \
|
|
"${checksums[$index]}" "${filenames[$index]}"; then
|
|
TAPM_CLEAN_TEMP_DIR "$temp_dir"
|
|
FINISH_FAILED_ACTION
|
|
return
|
|
fi
|
|
done
|
|
|
|
if ! apt-get update ||
|
|
! DEBIAN_FRONTEND=noninteractive apt-get install -y \
|
|
gnupg libcurl4t64 libncurses6 libxslt1.1 libgpm2 libtinfo6 ||
|
|
! gpg --batch --yes --dearmor --output "$dell_keyring" "$dell_key"; then
|
|
TAPM_CLEAN_TEMP_DIR "$temp_dir"
|
|
echo -e "${idsCL[LightRed]}Unable to prepare the Dell OMSA repository.${idsCL[Default]}"
|
|
FINISH_FAILED_ACTION
|
|
return
|
|
fi
|
|
|
|
if ! printf '%s\n' \
|
|
'deb [signed-by=/etc/apt/keyrings/linux.dell.com.gpg] https://linux.dell.com/repo/community/openmanage/11000/jammy jammy main' \
|
|
>"$dell_source" ||
|
|
! mkdir -p /etc/apt/keyrings ||
|
|
! install -m 0644 "$dell_keyring" /etc/apt/keyrings/linux.dell.com.gpg ||
|
|
! install -m 0644 "$dell_source" /etc/apt/sources.list.d/linux.dell.com.list; then
|
|
TAPM_CLEAN_TEMP_DIR "$temp_dir"
|
|
echo -e "${idsCL[LightRed]}Unable to write the Dell OMSA repository configuration.${idsCL[Default]}"
|
|
FINISH_FAILED_ACTION
|
|
return
|
|
fi
|
|
|
|
if ! apt-get update; then
|
|
TAPM_CLEAN_TEMP_DIR "$temp_dir"
|
|
echo -e "${idsCL[LightRed]}The Dell OMSA repository could not be refreshed.${idsCL[Default]}"
|
|
FINISH_FAILED_ACTION
|
|
return
|
|
fi
|
|
|
|
if ! dpkg -i "${package_paths[@]}"; then
|
|
if ! DEBIAN_FRONTEND=noninteractive apt-get install --fix-broken -y ||
|
|
! dpkg -i "${package_paths[@]}"; then
|
|
TAPM_CLEAN_TEMP_DIR "$temp_dir"
|
|
echo -e "${idsCL[LightRed]}The legacy OMSA compatibility packages could not be installed.${idsCL[Default]}"
|
|
FINISH_FAILED_ACTION
|
|
return
|
|
fi
|
|
fi
|
|
|
|
if ! DEBIAN_FRONTEND=noninteractive apt-get install -y srvadmin-all ||
|
|
! TAPM_PACKAGE_INSTALLED srvadmin-all ||
|
|
[[ ! -x /opt/dell/srvadmin/sbin/srvadmin-services.sh ]] ||
|
|
! /opt/dell/srvadmin/sbin/srvadmin-services.sh start ||
|
|
! TAPM_WAIT_FOR_TCP_PORT 1311 30; then
|
|
TAPM_CLEAN_TEMP_DIR "$temp_dir"
|
|
echo -e "${idsCL[LightRed]}Dell OMSA installation failed or port 1311 did not become available.${idsCL[Default]}"
|
|
FINISH_FAILED_ACTION
|
|
return
|
|
fi
|
|
|
|
TAPM_CLEAN_TEMP_DIR "$temp_dir"
|
|
echo -e "\n${idsCL[Green]}Dell OMSA has been installed and verified.${idsCL[Default]}"
|
|
echo -e "\n${idsCL[LightCyan]}Available at: ${idsCL[LightGreen]}https://${RNIP}:1311${idsCL[Default]}"
|
|
FINISH_ACTION
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
TAPM_INSTALL_PROXCLMC() {
|
|
local key_url='https://git.gyptazy.com/api/packages/gyptazy/debian/repository.key'
|
|
local keyring='/etc/apt/keyrings/gyptazy.asc'
|
|
local repository_file='/etc/apt/sources.list.d/gyptazy.list'
|
|
local repository_line='deb [signed-by=/etc/apt/keyrings/gyptazy.asc] https://packages.gyptazy.com/api/packages/gyptazy/debian trixie main'
|
|
local temp_dir
|
|
local temp_key
|
|
|
|
TAPM_PACKAGE_INSTALLED proxclmc && command -v proxclmc >/dev/null 2>&1 &&
|
|
return 0
|
|
|
|
TAPM_CREATE_TEMP_DIR proxclmc || return 1
|
|
temp_dir="$TAPM_TEMP_DIR"
|
|
temp_key="${temp_dir}/gyptazy.asc"
|
|
|
|
TAPM_DOWNLOAD_HTTPS "$key_url" "$temp_key" "ProxCLMC repository key" ||
|
|
return 1
|
|
if ! command -v gpg >/dev/null 2>&1 ||
|
|
! gpg --batch --show-keys "$temp_key" >/dev/null 2>&1; then
|
|
echo -e "${idsCL[LightRed]}The downloaded ProxCLMC repository key is not a valid OpenPGP key.${idsCL[Default]}"
|
|
return 1
|
|
fi
|
|
|
|
if ! install -d -m 0755 /etc/apt/keyrings ||
|
|
! install -m 0644 "$temp_key" "$keyring" ||
|
|
! printf '%s\n' "$repository_line" >"$repository_file" ||
|
|
! chmod 0644 "$repository_file"; then
|
|
echo -e "${idsCL[LightRed]}Could not configure the ProxCLMC package repository.${idsCL[Default]}"
|
|
return 1
|
|
fi
|
|
|
|
if ! apt-get update ||
|
|
! apt-get install -y proxclmc ||
|
|
! command -v proxclmc >/dev/null 2>&1; then
|
|
echo -e "${idsCL[LightRed]}ProxCLMC could not be installed.${idsCL[Default]}"
|
|
return 1
|
|
fi
|
|
|
|
TAPM_CLEAN_TEMP_DIR "$temp_dir"
|
|
}
|
|
|
|
TAPM_CLUSTER_QEMU_GUESTS() {
|
|
pvesh get /cluster/resources --type vm --output-format json 2>/dev/null |
|
|
python3 -c '
|
|
import json
|
|
import sys
|
|
|
|
for guest in json.load(sys.stdin):
|
|
if guest.get("type") != "qemu":
|
|
continue
|
|
print(
|
|
guest.get("vmid", ""),
|
|
str(guest.get("name", ""))
|
|
.replace("\x1f", " ")
|
|
.replace("\r", " ")
|
|
.replace("\n", " "),
|
|
guest.get("node", ""),
|
|
"yes" if guest.get("template") in (1, True, "1") else "no",
|
|
sep="\x1f",
|
|
)
|
|
'
|
|
}
|
|
|
|
TAPM_QEMU_CPU_MODEL() {
|
|
local vmid="$1"
|
|
local config_output
|
|
local cpu_model
|
|
|
|
config_output="$(qm config "$vmid" 2>/dev/null)" || return 1
|
|
cpu_model="$(
|
|
awk -F': ' '$1 == "cpu" { print $2; exit }' <<< "$config_output"
|
|
)"
|
|
[[ -n "$cpu_model" ]] || cpu_model='kvm64'
|
|
printf '%s\n' "$cpu_model"
|
|
}
|
|
|
|
DETECT_CPU() {
|
|
local answer
|
|
local cpu_model
|
|
local current_cpu
|
|
local guest
|
|
local guest_output
|
|
local name
|
|
local node
|
|
local template
|
|
local vmid
|
|
local -a changes=()
|
|
local -a failures=()
|
|
local -a successes=()
|
|
|
|
for command in apt-get curl gpg install pvesh python3 qm; do
|
|
if ! command -v "$command" >/dev/null 2>&1; then
|
|
echo -e "${idsCL[LightRed]}${command} is required for CPU compatibility detection.${idsCL[Default]}"
|
|
FINISH_FAILED_ACTION
|
|
return
|
|
fi
|
|
done
|
|
|
|
if ! TAPM_INSTALL_PROXCLMC; then
|
|
FINISH_FAILED_ACTION
|
|
return
|
|
fi
|
|
|
|
echo -e "\n${idsCL[LightCyan]}Analyzing CPU compatibility across the cluster...${idsCL[Default]}"
|
|
cpu_model="$(proxclmc --list-only 2>/dev/null)" || {
|
|
echo -e "${idsCL[LightRed]}ProxCLMC could not determine a compatible CPU model.${idsCL[Default]}"
|
|
FINISH_FAILED_ACTION
|
|
return
|
|
}
|
|
cpu_model="${cpu_model//$'\r'/}"
|
|
cpu_model="${cpu_model//$'\n'/}"
|
|
if [[ ! "$cpu_model" =~ ^x86-64-v(1|2-AES|3|4)$ ]]; then
|
|
echo -e "${idsCL[LightRed]}ProxCLMC returned an unexpected CPU model: ${cpu_model:-empty}.${idsCL[Default]}"
|
|
FINISH_FAILED_ACTION
|
|
return
|
|
fi
|
|
|
|
guest_output="$(TAPM_CLUSTER_QEMU_GUESTS)" || {
|
|
echo -e "${idsCL[LightRed]}Could not read cluster QEMU resources.${idsCL[Default]}"
|
|
FINISH_FAILED_ACTION
|
|
return
|
|
}
|
|
if [[ -z "$guest_output" ]]; then
|
|
echo -e "\n${idsCL[LightCyan]}No QEMU VMs or templates were found in the cluster.${idsCL[Default]}"
|
|
FINISH_ACTION
|
|
return
|
|
fi
|
|
|
|
while IFS=$'\x1f' read -r vmid name node template; do
|
|
[[ -n "$vmid" ]] || continue
|
|
current_cpu="$(TAPM_QEMU_CPU_MODEL "$vmid")" || {
|
|
echo -e "${idsCL[LightRed]}Could not read the CPU configuration for VMID ${vmid}.${idsCL[Default]}"
|
|
FINISH_FAILED_ACTION
|
|
return
|
|
}
|
|
[[ "$current_cpu" == "$cpu_model" ]] && continue
|
|
changes+=(
|
|
"${vmid}"$'\x1f'"${name}"$'\x1f'"${node}"$'\x1f'"${template}"$'\x1f'"${current_cpu}"
|
|
)
|
|
done <<< "$guest_output"
|
|
|
|
if (( ${#changes[@]} == 0 )); then
|
|
echo -e "\n${idsCL[Green]}All QEMU VMs and templates already use ${cpu_model}.${idsCL[Default]}"
|
|
FINISH_ACTION
|
|
return
|
|
fi
|
|
|
|
printf '\nRecommended cluster CPU model: %s\n' "$cpu_model"
|
|
printf '\n%-7s %-28s %-20s %-10s %-24s %s\n' \
|
|
'VMID' 'NAME' 'NODE' 'TEMPLATE' 'CURRENT CPU' 'PROPOSED CPU'
|
|
printf '%-7s %-28s %-20s %-10s %-24s %s\n' \
|
|
'-------' '----------------------------' '--------------------' \
|
|
'----------' '------------------------' '----------------'
|
|
for guest in "${changes[@]}"; do
|
|
IFS=$'\x1f' read -r vmid name node template current_cpu <<< "$guest"
|
|
printf '%-7s %-28.28s %-20.20s %-10s %-24.24s %s\n' \
|
|
"$vmid" "${name:-unnamed}" "$node" "$template" "$current_cpu" "$cpu_model"
|
|
done
|
|
|
|
echo -en "\n${idsCL[LightCyan]}Apply this CPU model to ${#changes[@]} VM(s) and template(s) (y/N)?${idsCL[Default]} "
|
|
read -r -n 1 answer
|
|
echo
|
|
[[ "$answer" =~ ^[Yy]$ ]] || return
|
|
|
|
for guest in "${changes[@]}"; do
|
|
IFS=$'\x1f' read -r vmid name node template current_cpu <<< "$guest"
|
|
if qm set "$vmid" --cpu "$cpu_model" &&
|
|
[[ "$(TAPM_QEMU_CPU_MODEL "$vmid")" == "$cpu_model" ]]; then
|
|
successes+=("$guest")
|
|
else
|
|
failures+=("$guest")
|
|
fi
|
|
done
|
|
|
|
printf '\nCPU model update summary:\n'
|
|
printf ' Successful: %d\n' "${#successes[@]}"
|
|
printf ' Failed: %d\n' "${#failures[@]}"
|
|
|
|
if (( ${#failures[@]} > 0 )); then
|
|
printf '\nFailed VM/template updates:\n'
|
|
for guest in "${failures[@]}"; do
|
|
IFS=$'\x1f' read -r vmid name node template current_cpu <<< "$guest"
|
|
printf ' %s (%s) on %s\n' "$vmid" "${name:-unnamed}" "$node"
|
|
done
|
|
echo -e "\n${idsCL[LightRed]}One or more CPU model updates failed.${idsCL[Default]}"
|
|
FINISH_FAILED_ACTION
|
|
return
|
|
fi
|
|
|
|
echo -e "\n${idsCL[Green]}The CPU model was updated to ${cpu_model}.${idsCL[Default]}"
|
|
echo -e "${idsCL[LightCyan]}Running VMs must be fully shut down and started again before the new CPU model takes effect.${idsCL[Default]}"
|
|
FINISH_ACTION
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
HA_NODE_IN_MAINTENANCE() {
|
|
local node="$1"
|
|
local ha_status
|
|
|
|
ha_status="$(ha-manager status 2>/dev/null)" || return 2
|
|
grep -F "lrm ${node} " <<< "$ha_status" |
|
|
grep -q "maintenance mode"
|
|
}
|
|
|
|
WAIT_FOR_HA_MAINTENANCE_STATE() {
|
|
local node="$1"
|
|
local expected_state="$2"
|
|
local attempts="${3:-30}"
|
|
local attempt=0
|
|
local active=0
|
|
local state_result
|
|
|
|
while (( attempt < attempts )); do
|
|
active=0
|
|
HA_NODE_IN_MAINTENANCE "$node"
|
|
state_result=$?
|
|
[[ "$state_result" -eq 0 ]] && active=1
|
|
if [[ "$state_result" -gt 1 ]]; then
|
|
sleep 1
|
|
((attempt++))
|
|
continue
|
|
fi
|
|
if [[ "$expected_state" == "enabled" && "$active" -eq 1 ]] ||
|
|
[[ "$expected_state" == "disabled" && "$active" -eq 0 ]]; then
|
|
return 0
|
|
fi
|
|
sleep 1
|
|
((attempt++))
|
|
done
|
|
return 1
|
|
}
|
|
|
|
MAINTENANCE_MODE() {
|
|
local choice
|
|
local local_node
|
|
local maintenance_state
|
|
local_node="$(hostname -s)"
|
|
|
|
HA_NODE_IN_MAINTENANCE "$local_node"
|
|
maintenance_state=$?
|
|
if [[ "$maintenance_state" -gt 1 ]]; then
|
|
echo -e "\n${idsCL[LightRed]}Could not read HA maintenance status for ${local_node}.${idsCL[Default]}"
|
|
FINISH_FAILED_ACTION
|
|
return
|
|
fi
|
|
|
|
if [[ "$maintenance_state" -eq 0 ]]; 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 -r -n 1 choice
|
|
case "$choice" in
|
|
[Nn]) echo;;
|
|
*) echo
|
|
if [[ "$maintenance_state" -eq 0 ]]; then
|
|
if ! ha-manager crm-command node-maintenance disable "$local_node" ||
|
|
! WAIT_FOR_HA_MAINTENANCE_STATE "$local_node" disabled; then
|
|
echo -e "\n${idsCL[LightRed]}Failed to take ${local_node} out of HA maintenance mode.${idsCL[Default]}"
|
|
FINISH_FAILED_ACTION
|
|
return
|
|
fi
|
|
echo -e "\n${idsCL[Green]}This host will be taken out of maintenance mode${idsCL[Default]}\n"
|
|
else
|
|
if ! ha-manager crm-command node-maintenance enable "$local_node"; then
|
|
echo -e "\n${idsCL[LightRed]}Failed to request HA maintenance mode for ${local_node}.${idsCL[Default]}"
|
|
FINISH_FAILED_ACTION
|
|
return
|
|
fi
|
|
echo -e "\n${idsCL[Green]}This host will be entered into maintenance mode${idsCL[Default]}\n"
|
|
if ! bash /opt/idssys/ta-proxmenu/inc/evacuate-proxmox-node.sh; then
|
|
echo -e "\n${idsCL[LightRed]}Evacuation did not complete. ${local_node} remains in HA maintenance mode.${idsCL[Default]}"
|
|
FINISH_FAILED_ACTION
|
|
return
|
|
fi
|
|
fi
|
|
FINISH_ACTION
|
|
;;
|
|
esac
|
|
}
|
|
|
|
INSTALL_KEEPALIVE() {
|
|
echo
|
|
|
|
bash /opt/idssys/ta-proxmenu/inc/deploy-proxmox-keepalived.sh
|
|
|
|
echo -e "\n${idsCL[Green]}Keepalive has been installed${idsCL[Default]}"
|
|
FINISH_ACTION
|
|
}
|
|
|
|
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
|
|
if TAPM_GIT_WORKTREE_DIRTY "$FOLDER"; then
|
|
WRITE_UPDATE_CACHE "dirty" "$branch" "$local_commit" ""
|
|
return
|
|
fi
|
|
|
|
if ! TAPM_GIT_FETCH_BRANCH "$FOLDER" "$branch" 30 >/dev/null 2>&1; then
|
|
WRITE_UPDATE_CACHE "unavailable" "$branch" "$local_commit" ""
|
|
return
|
|
fi
|
|
remote_commit="$(git -C "$FOLDER" rev-parse "refs/remotes/origin/${branch}" 2>/dev/null)"
|
|
status="$(TAPM_GIT_BRANCH_STATE "$FOLDER" "$branch")"
|
|
|
|
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 current_dirty=0
|
|
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)"
|
|
TAPM_GIT_WORKTREE_DIRTY "$FOLDER" && current_dirty=1
|
|
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|behind|ahead|diverged|dirty|unavailable)$ ]] &&
|
|
{ [[ "$cached_status" == "dirty" && "$current_dirty" -eq 1 ]] ||
|
|
[[ "$cached_status" != "dirty" && "$current_dirty" -eq 0 ]]; }; 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
|
|
behind)
|
|
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]}"
|
|
;;
|
|
ahead)
|
|
echo -e "${idsCL[LightYellow]}The current branch has local commits not on origin.${idsCL[Default]}"
|
|
;;
|
|
diverged)
|
|
echo -e "${idsCL[LightRed]}The current branch has diverged from origin; automatic update is disabled.${idsCL[Default]}"
|
|
;;
|
|
dirty)
|
|
echo -e "${idsCL[LightYellow]}The TA-ProxMenu repository has local file changes.${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
|
|
behind)
|
|
version_display="${idsCL[LightYellow]}${VERS} ** UPDATE AVAILABLE **${idsCL[Default]}"
|
|
;;
|
|
ahead)
|
|
version_display="${idsCL[LightYellow]}${VERS} ** LOCAL COMMITS **${idsCL[Default]}"
|
|
;;
|
|
diverged)
|
|
version_display="${idsCL[LightRed]}${VERS} ** BRANCH DIVERGED **${idsCL[Default]}"
|
|
;;
|
|
dirty)
|
|
version_display="${idsCL[LightYellow]}${VERS} ** LOCAL CHANGES **${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
|
|
|
|
while true; do
|
|
labels=("Run ProxMenux post-install configuration")
|
|
values=("post_install")
|
|
[ -f /opt/.PROXMENUX_POST_INSTALL ] &&
|
|
labels[0]="Run ProxMenux post-install configuration (previously run)"
|
|
labels+=("Detect CPU model for live migrations")
|
|
values+=("cpu")
|
|
|
|
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
|
|
values+=("virtio")
|
|
|
|
command -v glances >/dev/null 2>&1 &&
|
|
labels+=("Glances CLI monitor (installed)") ||
|
|
labels+=("Install Glances CLI monitor")
|
|
values+=("glances")
|
|
|
|
if TAPM_OMSA_SUPPORTED_HARDWARE; then
|
|
TAPM_PACKAGE_INSTALLED srvadmin-all &&
|
|
labels+=("Dell OMSA - Legacy Dell Hosts (installed)") ||
|
|
labels+=("Install Dell OMSA - Legacy Dell Hosts")
|
|
values+=("omsa")
|
|
fi
|
|
|
|
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
|
|
local target_state=''
|
|
|
|
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 ! TAPM_GIT_FETCH_BRANCH "$FOLDER" "$target_branch" 30; 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
|
|
target_state="$(
|
|
TAPM_GIT_RELATION "$FOLDER" "refs/heads/${target_branch}" \
|
|
"refs/remotes/origin/${target_branch}"
|
|
)" || {
|
|
echo -e "${idsCL[Red]}Could not compare local and remote '${target_branch}'.${idsCL[Default]}"
|
|
ENTER2CONTINUE
|
|
return
|
|
}
|
|
case "$target_state" in
|
|
ahead)
|
|
echo -e "${idsCL[LightYellow]}The local '${target_branch}' branch has commits not on origin.${idsCL[Default]}"
|
|
echo "Branch switching was refused to preserve those commits."
|
|
ENTER2CONTINUE
|
|
return
|
|
;;
|
|
diverged)
|
|
echo -e "${idsCL[LightRed]}The local '${target_branch}' branch has diverged from origin.${idsCL[Default]}"
|
|
echo "Branch switching was refused; manual Git review is required."
|
|
ENTER2CONTINUE
|
|
return
|
|
;;
|
|
esac
|
|
|
|
git -C "$FOLDER" switch "$target_branch" || {
|
|
ENTER2CONTINUE
|
|
return
|
|
}
|
|
else
|
|
git -C "$FOLDER" switch --create "$target_branch" \
|
|
--track "origin/${target_branch}" || {
|
|
ENTER2CONTINUE
|
|
return
|
|
}
|
|
fi
|
|
|
|
if [[ "$target_state" == "behind" ]]; then
|
|
if ! TAPM_GIT_FAST_FORWARD "$FOLDER" "$target_branch"; then
|
|
echo -e "${idsCL[Red]}Failed to fast-forward '${target_branch}'; no commits were discarded.${idsCL[Default]}"
|
|
ENTER2CONTINUE
|
|
return
|
|
fi
|
|
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
|
|
behind)
|
|
labels=("Install available update")
|
|
;;
|
|
current)
|
|
labels=("TA-ProxMenu is current")
|
|
;;
|
|
ahead)
|
|
labels=("Local branch is ahead of origin")
|
|
;;
|
|
diverged)
|
|
labels=("Local branch has diverged from origin")
|
|
;;
|
|
dirty)
|
|
labels=("Local repository has uncommitted changes")
|
|
;;
|
|
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" != "behind" ]]; 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
|