This commit is contained in:
David Schroeder
2026-07-25 11:46:04 -05:00
parent cc83cce667
commit 376d029fc8
4 changed files with 380 additions and 98 deletions
+5 -3
View File
@@ -11,8 +11,10 @@ Run as `root` on a Proxmox VE host:
bash <(curl -fsSL https://go.scity.us/install-tapm) bash <(curl -fsSL https://go.scity.us/install-tapm)
``` ```
The installed launcher loads the shared iDSSYS defaults, checks for updates, The installed launcher loads the shared iDSSYS defaults and opens the
and opens the interactive menu. interactive menu. Update availability is checked in the background and cached;
updates are installed only when explicitly selected or requested with
`tapm update`.
## Direct actions ## Direct actions
@@ -29,7 +31,7 @@ proxmenux Install or open ProxMenux
virtio Download current VirtIO drivers virtio Download current VirtIO drivers
sentinelone Install the SentinelOne agent sentinelone Install the SentinelOne agent
screenconnect Install the ScreenConnect agent screenconnect Install the ScreenConnect agent
restart Restart local Proxmox services restart Restart core local Proxmox management services
cpu Detect and optionally apply a migration-safe CPU model cpu Detect and optionally apply a migration-safe CPU model
maintenance Toggle local HA maintenance mode maintenance Toggle local HA maintenance mode
keepalived Deploy Keepalived across the cluster keepalived Deploy Keepalived across the cluster
+1 -1
View File
@@ -3,7 +3,7 @@
action="${1:-}" action="${1:-}"
FOLDER='/opt/idssys/ta-proxmenu' FOLDER='/opt/idssys/ta-proxmenu'
VERS='2026.7.25-15' VERS='2026.7.25-16'
noupdate=' ' noupdate=' '
+297 -25
View File
@@ -235,24 +235,88 @@ DETECT_CPU(){
} }
RESTART_PVE_SERVICES(){ RESTART_SERVICE_GROUP() {
if [ "${1}" == "" ]; then local description="$1"
echo -en "${idsCL[LightCyan]}Would you like to restart all Proxmox services on the local host (Y/n)?${idsCL[Default]} " shift
read -n 1 choice local -a services=("$@")
local service
local failed=0
local choice
if (( ${RESTART_ASSUME_YES:-0} == 1 )); then
choice="y"
else else
choice=${1} echo -en "${idsCL[LightCyan]}Restart ${description} on this host (Y/n)?${idsCL[Default]} "
read -n 1 choice
echo
fi fi
case "${choice}" in [[ "$choice" =~ ^[Nn]$ ]] && return
[Nn]) echo;;
*) echo echo -e "\n${idsCL[Yellow]}Restarting ${description}...${idsCL[Default]}"
echo -en "\n${idsCL[Yellow]}Restarting services ... " if ! systemctl restart "${services[@]}"; then
#systemctl restart pve-cluster pvedaemon pvestatd pveproxy pve-ha-lrm pve-ha-crm failed=1
systemctl restart pve-cluster pvedaemon pvestatd pveproxy fi
echo -e "${idsCL[Green]}Done${idsCL[Default]}"
echo -e "\n${idsCL[Green]}This hosts Proxmox services have been restarted${idsCL[Default]}\n" 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 FINISH_ACTION
;; }
esac
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(){ MAINTENANCE_MODE(){
@@ -295,12 +359,151 @@ INSTALL_KEEPALIVE() {
esac 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() { 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 clear
echo echo
echo -e " ${idsCL[Green]}TA-ProxMenu - Proxmox Setup Scripts${idsCL[Default]} ${VERS}" echo -e " ${idsCL[Green]}TA-ProxMenu - Proxmox Setup Scripts${idsCL[Default]} ${version_display}"
echo -e "${idsCL[Green]}---------------------------------------------------------------------------${idsCL[Default]}" echo -e "${idsCL[Green]}---------------------------------------------------------------------------${idsCL[Default]}"
echo -e " Hostname: ${idsCL[Cyan]}$(hostname -s)${idsCL[Default]}" echo -e " Hostname: ${idsCL[Cyan]}$(hostname -s)${idsCL[Default]}"
echo -e " IP Address: ${idsCL[Cyan]}${RNIP:-Unavailable}${idsCL[Default]}" echo -e " IP Address: ${idsCL[Cyan]}${RNIP:-Unavailable}${idsCL[Default]}"
@@ -340,7 +543,12 @@ SELECT_MENU() {
echo " ↑/↓ Navigate Enter Select Number Quick Select Q Quit" echo " ↑/↓ Navigate Enter Select Number Quick Select Q Quit"
fi fi
key=""
if [[ "$UPDATE_STATUS" == "checking" ]]; then
IFS= read -rsn1 -t 1 key || continue
else
IFS= read -rsn1 key IFS= read -rsn1 key
fi
case "$key" in case "$key" in
"") "")
MENU_SELECTION="${values_ref[$selected]}" MENU_SELECTION="${values_ref[$selected]}"
@@ -494,7 +702,7 @@ MONITORING_MENU() {
CLUSTER_MENU() { CLUSTER_MENU() {
local -a labels local -a labels
local -a values=("maintenance" "restart" "keepalived") local -a values=("maintenance" "services" "keepalived")
while true; do while true; do
if ha-manager status | grep -F "$(hostname -s)" | grep -q "maintenance mode"; then if ha-manager status | grep -F "$(hostname -s)" | grep -q "maintenance mode"; then
@@ -503,7 +711,7 @@ CLUSTER_MENU() {
labels=("Put this host into maintenance mode and evacuate guests") labels=("Put this host into maintenance mode and evacuate guests")
fi fi
labels+=("Restart local Proxmox services") labels+=("Proxmox service recovery")
dpkg-query -W -f='${Status}' keepalived 2>/dev/null | grep -q "install ok installed" && dpkg-query -W -f='${Status}' keepalived 2>/dev/null | grep -q "install ok installed" &&
labels+=("Deploy/reconfigure Keepalived (installed locally)") || labels+=("Deploy/reconfigure Keepalived (installed locally)") ||
labels+=("Deploy Keepalived on all cluster hosts") labels+=("Deploy Keepalived on all cluster hosts")
@@ -511,7 +719,7 @@ CLUSTER_MENU() {
SELECT_MENU "Cluster & Maintenance" labels values SELECT_MENU "Cluster & Maintenance" labels values
case "$MENU_SELECTION" in case "$MENU_SELECTION" in
maintenance) MAINTENANCE_MODE;; maintenance) MAINTENANCE_MODE;;
restart) RESTART_PVE_SERVICES;; services) SERVICE_RECOVERY_MENU;;
keepalived) INSTALL_KEEPALIVE;; keepalived) INSTALL_KEEPALIVE;;
back) return;; back) return;;
quit) EXIT1; exit 0;; quit) EXIT1; exit 0;;
@@ -519,17 +727,81 @@ CLUSTER_MENU() {
done done
} }
UTILITIES_MENU() { SERVICE_RECOVERY_MENU() {
local -a labels=("Check for script updates" "Version and installation information") local -a labels=(
local -a values=("update" "about") "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 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
}
UTILITIES_MENU() {
local -a labels
local -a values=("install_update" "check_update" "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" "Version and installation information")
SELECT_MENU "Utilities" labels values SELECT_MENU "Utilities" labels values
case "$MENU_SELECTION" in case "$MENU_SELECTION" in
update) install_update)
/opt/idssys/ta-proxmenu/run.sh update if [[ "$UPDATE_STATUS" != "available" ]]; then
read -r -p " Press ENTER to return..." _ 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;;
about) SHOW_ABOUT;; about) SHOW_ABOUT;;
back) return;; back) return;;
quit) EXIT1; exit 0;; quit) EXIT1; exit 0;;
+68 -60
View File
@@ -1,79 +1,87 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# TA-Proxmenu preloader # TA-ProxMenu preloader
[ "${2:-}" != "q" ] && source /opt/idssys/defaults/colors.inc [ "${2:-}" != "q" ] && source /opt/idssys/defaults/colors.inc
source /opt/idssys/defaults/default.inc source /opt/idssys/defaults/default.inc
source /opt/idssys/ta-proxmenu/defaults.inc source /opt/idssys/ta-proxmenu/defaults.inc
if [[ "${noupdate}" != *" ${1:-} "* ]] && [[ "${noupdate}" != *" ${2:-} "* ]]; then UPDATE_REPOSITORY() {
if curl -m 3 -s --head --request GET https://git.scity.us | grep "HTTP/2 200" > /dev/null; then local repository="$1"
if [ "${1}" != "tapm" ]; then local branch="$2"
echo -en "${idsCL[LightCyan]}Checking for updates...${idsCL[Default]}" local label="$3"
echo "" local local_commit
udtd=0 local remote_commit
cd "$repository" || return 1
local_commit="$(git rev-parse HEAD 2>/dev/null)" || return 1
remote_commit="$(git ls-remote origin "refs/heads/${branch}" | cut -f1)"
if [ -z "$remote_commit" ]; then
echo -e "${idsCL[Red]}Could not find branch '${branch}' for ${label}${idsCL[Default]}"
return 1
fi fi
if [ "${1}" != "tapm" ]; then if [ "$local_commit" = "$remote_commit" ]; then
if [ ! -d /opt/idssys/defaults ]; then echo -e "${idsCL[Green]}${label} is current (${branch})${idsCL[Default]}"
git clone https://git.scity.us/voltron/iDS-Defaults.git /opt/idssys/defaults return 0
else
cd /opt/idssys/defaults
if [ "$(git rev-parse HEAD)" != "$(git ls-remote origin refs/heads/master | cut -f1)" ]; then
if [ "${1}" != "tapm" ]; then
echo -en "\e[1A";
echo -en "\e[0K\r${idsCL[LightCyan]}Updating iDSSYS-Defaults...${idsCL[Default]}"
udtd=1
fi
git fetch origin master >/dev/null 2>&1
git reset --hard origin/master >/dev/null 2>&1
git reflog expire --expire=now --all >/dev/null 2>&1
git repack -ad >/dev/null 2>&1
git prune >/dev/null 2>&1
git pull >/dev/null 2>&1
[ "${1}" != "tapm" ] && echo -e "${idsCL[Green]}Done${idsCL[Default]}"
fi
fi
fi fi
cd /opt/idssys/ta-proxmenu echo -en "${idsCL[LightCyan]}Updating ${label} (${branch})...${idsCL[Default]}"
current_branch="$(git branch --show-current)" if git fetch origin "$branch" >/dev/null 2>&1 &&
remote_commit="" git reset --hard "origin/${branch}" >/dev/null 2>&1; then
if [ -n "$current_branch" ]; then echo -e " ${idsCL[Green]}Done${idsCL[Default]}"
remote_commit="$(git ls-remote origin "refs/heads/${current_branch}" | cut -f1)" return 0
fi fi
echo -e " ${idsCL[Red]}Failed${idsCL[Default]}"
return 1
}
INSTALL_UPDATES() {
local current_branch
local update_failed=0
echo -e "${idsCL[LightCyan]}Checking for updates...${idsCL[Default]}"
if ! curl --fail --silent --show-error --head \
--connect-timeout 3 --max-time 10 https://git.scity.us >/dev/null; then
echo -e "${idsCL[Red]}Could not connect to git.scity.us${idsCL[Default]}"
return 1
fi
UPDATE_REPOSITORY /opt/idssys/defaults master "iDSSYS Defaults" ||
update_failed=1
current_branch="$(git -C /opt/idssys/ta-proxmenu branch --show-current)"
if [ -z "$current_branch" ]; then if [ -z "$current_branch" ]; then
echo -e "${idsCL[Red]}TA-Proxmenu is in a detached HEAD state; automatic updates were skipped${idsCL[Default]}" echo -e "${idsCL[Red]}TA-ProxMenu is in a detached HEAD state; update skipped${idsCL[Default]}"
elif [ -z "$remote_commit" ]; then update_failed=1
echo -e "${idsCL[Red]}Could not find branch '${current_branch}' on the TA-Proxmenu origin; automatic updates were skipped${idsCL[Default]}"
elif [ "$(git rev-parse HEAD)" != "$remote_commit" ]; then
if [ "${1}" != "tapm" ]; then
[ ${udtd} -eq 0 ] && echo -en "\e[1A";
echo -en "\e[0K\r${idsCL[LightCyan]}Updating TA-Proxmenu (${current_branch})...${idsCL[Default]}"
fi
git fetch origin "$current_branch" >/dev/null 2>&1
git reset --hard "origin/${current_branch}" >/dev/null 2>&1
git reflog expire --expire=now --all >/dev/null 2>&1
git repack -ad >/dev/null 2>&1
git prune >/dev/null 2>&1
if [ "${1}" != "tapm" ]; then
source /opt/idssys/ta-proxmenu/defaults.inc
# echo -en "\e[1A";
# echo -e "\e[0K\r ${idsCL[Green]}Updated to v${VERS}${idsCL[Default]}"
echo -e " ${idsCL[Green]}Updated to v${VERS}${idsCL[Default]}\n"
fi
elif [ "${1}" != "tapm" ] && [ ${udtd} -eq 0 ]; then
echo -e "\e[1A\e[0K\r ${idsCL[Green]}No updates available${idsCL[Default]}\n"
fi
else else
echo -e "${idsCL[Red]}Could not connect to 'git.scity.us' for updates${idsCL[Default]}" UPDATE_REPOSITORY /opt/idssys/ta-proxmenu "$current_branch" "TA-ProxMenu" ||
fi update_failed=1
fi fi
if [ "${1}" != "tapm" ] && [ "${1}" != "update" ] && [ "${1}" != "u" ]; then rm -f /var/cache/ta-proxmenu/update-status 2>/dev/null || true
/opt/idssys/ta-proxmenu/proxmenu-scripts.sh "${1:-}" "${2:-}" "${3:-}" "${4:-}"
if (( update_failed == 0 )); then
source /opt/idssys/ta-proxmenu/defaults.inc
echo -e "\n${idsCL[Green]}Update check complete. Installed version: ${VERS}${idsCL[Default]}"
return 0
fi fi
return 1
}
case "${1:-}" in
update|u)
INSTALL_UPDATES
exit $?
;;
tapm)
exit 0 exit 0
;;
esac
/opt/idssys/ta-proxmenu/proxmenu-scripts.sh \
"${1:-}" "${2:-}" "${3:-}" "${4:-}"
exit $?