mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-06-11 19:07:01 +00:00
A user who rode the beta channel and later switched back to stable keeps a leftover beta_version.txt under /usr/local/share/proxmenux/. The `menu` launcher's beta-mode update check (`check_updates_beta`) short-circuits when that file isn't present, but it stays put across stable installs and updates today, so the user keeps seeing the "Beta update available" prompt on top of the legitimate stable one even though they're no longer on the beta channel. Drop the marker on every stable install/update, in both the update path (around the `cp ./version.txt` near the scripts-tree wipe) and the fresh-install path (the symmetric block lower in the file). The comment about which files survive a scripts-tree wipe is updated to no longer mention beta_version.txt, since that's exactly what we're removing. If the user re-opts into the beta program, install_proxmenux_beta re-creates the file — this only clears stale state that the user no longer has any way to update from anyway. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1236 lines
42 KiB
Bash
Executable File
1236 lines
42 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
# ==========================================================
|
|
# ProxMenux - A menu-driven toolkit for Proxmox VE management
|
|
# ==========================================================
|
|
# Author : MacRimi
|
|
# Contributors : cod378
|
|
# Subproject : ProxMenux Monitor (System Health & Web Dashboard)
|
|
# Copyright : (c) 2024-2025 MacRimi
|
|
# License : (GPL-3.0) (https://github.com/MacRimi/ProxMenux/blob/main/LICENSE)
|
|
# Version : 1.4
|
|
# Last Updated : 12/11/2025
|
|
# ==========================================================
|
|
# Description:
|
|
# This script installs and configures ProxMenux, a menu-driven
|
|
# toolkit for managing and optimizing Proxmox VE servers.
|
|
#
|
|
# - Ensures the script is run with root privileges.
|
|
# - Displays an installation confirmation prompt.
|
|
# - Installs required dependencies:
|
|
# • whiptail (interactive terminal menus)
|
|
# • curl (downloads and connectivity checks)
|
|
# • jq (JSON parsing)
|
|
# • Python 3 + venv (for translation support)
|
|
# - Creates the ProxMenux base directories and configuration files:
|
|
# • $BASE_DIR/config.json
|
|
# • $BASE_DIR/cache.json
|
|
# - Copies local project files into the target paths (offline mode by default):
|
|
# • scripts/* → $BASE_DIR/scripts/
|
|
# • utils.sh → $BASE_DIR/scripts/utils.sh
|
|
# • menu → $INSTALL_DIR/menu (main launcher)
|
|
# • install_proxmenux.sh → $BASE_DIR/install_proxmenux.sh
|
|
# - Sets correct permissions for all executables.
|
|
# - Displays the final instruction on how to start ProxMenux ("menu").
|
|
#
|
|
# Notes:
|
|
# - This installer supports both offline and online setups.
|
|
# - ProxMenux Monitor can be installed later as an optional module
|
|
# to provide real-time system monitoring and a web dashboard.
|
|
# ==========================================================
|
|
|
|
# Configuration ============================================
|
|
LOCAL_SCRIPTS="/usr/local/share/proxmenux/scripts"
|
|
INSTALL_DIR="/usr/local/bin"
|
|
BASE_DIR="/usr/local/share/proxmenux"
|
|
CONFIG_FILE="$BASE_DIR/config.json"
|
|
CACHE_FILE="$BASE_DIR/cache.json"
|
|
UTILS_FILE="$BASE_DIR/utils.sh"
|
|
LOCAL_VERSION_FILE="$BASE_DIR/version.txt"
|
|
MENU_SCRIPT="menu"
|
|
VENV_PATH="/opt/googletrans-env"
|
|
|
|
MONITOR_INSTALL_DIR="$BASE_DIR"
|
|
MONITOR_RUNTIME_DIR="$BASE_DIR/monitor-app"
|
|
MONITOR_SERVICE_FILE="/etc/systemd/system/proxmenux-monitor.service"
|
|
MONITOR_PORT=8008
|
|
|
|
# Offline installer envs
|
|
REPO_URL="https://github.com/MacRimi/ProxMenux.git"
|
|
TEMP_DIR="/tmp/proxmenux-install-$$"
|
|
|
|
# Load utility functions
|
|
NEON_PURPLE_BLUE="\033[38;5;99m"
|
|
WHITE="\033[38;5;15m"
|
|
RESET="\033[0m"
|
|
DARK_GRAY="\033[38;5;244m"
|
|
ORANGE="\033[38;5;208m"
|
|
YW="\033[33m"
|
|
YWB="\033[1;33m"
|
|
GN="\033[1;92m"
|
|
RD="\033[01;31m"
|
|
CL="\033[m"
|
|
BL="\033[36m"
|
|
DGN="\e[32m"
|
|
BGN="\e[1;32m"
|
|
DEF="\e[1;36m"
|
|
CUS="\e[38;5;214m"
|
|
BOLD="\033[1m"
|
|
BFR="\\r\\033[K"
|
|
HOLD="-"
|
|
BOR=" | "
|
|
CM="${GN}✓ ${CL}"
|
|
TAB=" "
|
|
|
|
|
|
# Create and display spinner
|
|
spinner() {
|
|
local frames=('⠋' '⠙' '⠹' '⠸' '⠼' '⠴' '⠦' '⠧' '⠇' '⠏')
|
|
local spin_i=0
|
|
local interval=0.1
|
|
printf "\e[?25l"
|
|
|
|
local color="${YW}"
|
|
|
|
while true; do
|
|
printf "\r ${color}%s${CL}" "${frames[spin_i]}"
|
|
spin_i=$(( (spin_i + 1) % ${#frames[@]} ))
|
|
sleep "$interval"
|
|
done
|
|
}
|
|
|
|
|
|
# Function to simulate typing effect
|
|
type_text() {
|
|
local text="$1"
|
|
local delay=0.05
|
|
for ((i=0; i<${#text}; i++)); do
|
|
echo -n "${text:$i:1}"
|
|
sleep $delay
|
|
done
|
|
echo
|
|
}
|
|
|
|
|
|
# Display info message with spinner
|
|
msg_info() {
|
|
local msg="$1"
|
|
echo -ne "${TAB}${YW}${HOLD}${msg}"
|
|
spinner &
|
|
SPINNER_PID=$!
|
|
}
|
|
|
|
|
|
# Display info2 message
|
|
msg_info2() {
|
|
local msg="$1"
|
|
echo -e "${TAB}${BOLD}${YW}${HOLD}${msg}${CL}"
|
|
}
|
|
|
|
|
|
|
|
# Display title script
|
|
msg_title() {
|
|
local msg="$1"
|
|
echo -e "\n"
|
|
echo -e "${TAB}${BOLD}${HOLD}${BOR}${msg}${BOR}${HOLD}${CL}"
|
|
echo -e "\n"
|
|
}
|
|
|
|
|
|
# Display warning or highlighted information message
|
|
msg_warn() {
|
|
if [ -n "$SPINNER_PID" ] && ps -p $SPINNER_PID > /dev/null; then
|
|
kill $SPINNER_PID > /dev/null
|
|
fi
|
|
printf "\e[?25h"
|
|
local msg="$1"
|
|
echo -e "${BFR}${TAB}${CL} ${YWB}${msg}${CL}"
|
|
}
|
|
|
|
|
|
# Display success message
|
|
msg_ok() {
|
|
if [ -n "$SPINNER_PID" ] && ps -p $SPINNER_PID > /dev/null; then
|
|
kill $SPINNER_PID > /dev/null
|
|
fi
|
|
printf "\e[?25h"
|
|
local msg="$1"
|
|
echo -e "${BFR}${TAB}${CM}${GN}${msg}${CL}"
|
|
}
|
|
|
|
|
|
# Display error message
|
|
msg_error() {
|
|
if [ -n "$SPINNER_PID" ] && ps -p $SPINNER_PID > /dev/null; then
|
|
kill $SPINNER_PID > /dev/null
|
|
fi
|
|
printf "\e[?25h"
|
|
local msg="$1"
|
|
echo -e "${BFR}${TAB}${RD}[ERROR] ${msg}${CL}"
|
|
}
|
|
|
|
|
|
|
|
|
|
show_proxmenux_logo() {
|
|
clear
|
|
|
|
if [[ -z "$SSH_TTY" && -z "$(who am i | awk '{print $NF}' | grep -E '([0-9]{1,3}\.){3}[0-9]{1,3}')" ]]; then
|
|
|
|
# Logo for terminal noVNC
|
|
|
|
LOGO=$(cat << "EOF"
|
|
\e[0m\e[38;2;61;61;61m▆\e[38;2;60;60;60m▄\e[38;2;54;54;54m▂\e[0m \e[38;2;0;0;0m \e[0m \e[38;2;54;54;54m▂\e[38;2;60;60;60m▄\e[38;2;61;61;61m▆\e[0m
|
|
\e[38;2;59;59;59;48;2;62;62;62m▏ \e[38;2;61;61;61;48;2;37;37;37m▇\e[0m\e[38;2;60;60;60m▅\e[38;2;56;56;56m▃\e[38;2;37;37;37m▁ \e[38;2;36;36;36m▁\e[38;2;56;56;56m▃\e[38;2;60;60;60m▅\e[38;2;61;61;61;48;2;37;37;37m▇\e[48;2;62;62;62m \e[0m\e[7m\e[38;2;60;60;60m▁\e[0m
|
|
\e[38;2;59;59;59;48;2;62;62;62m▏ \e[0m\e[7m\e[38;2;61;61;61m▂\e[0m\e[38;2;62;62;62;48;2;61;61;61m┈\e[48;2;62;62;62m \e[48;2;61;61;61m┈\e[0m\e[38;2;60;60;60m▆\e[38;2;57;57;57m▄\e[38;2;48;48;48m▂\e[0m \e[38;2;47;47;47m▂\e[38;2;57;57;57m▄\e[38;2;60;60;60m▆\e[38;2;62;62;62;48;2;61;61;61m┈\e[48;2;62;62;62m \e[48;2;61;61;61m┈\e[0m\e[7m\e[38;2;60;60;60m▂\e[38;2;57;57;57m▄\e[38;2;47;47;47m▆\e[0m \e[0m
|
|
\e[38;2;59;59;59;48;2;62;62;62m▏ \e[0m\e[38;2;32;32;32m▏\e[7m\e[38;2;39;39;39m▇\e[38;2;57;57;57m▅\e[38;2;60;60;60m▃\e[0m\e[38;2;40;40;40;48;2;61;61;61m▁\e[48;2;62;62;62m \e[38;2;54;54;54;48;2;61;61;61m┊\e[48;2;62;62;62m \e[38;2;39;39;39;48;2;61;61;61m▁\e[0m\e[7m\e[38;2;60;60;60m▃\e[38;2;57;57;57m▅\e[38;2;38;38;38m▇\e[0m \e[38;2;193;60;2m▃\e[38;2;217;67;2m▅\e[38;2;225;70;2m▇\e[0m
|
|
\e[38;2;59;59;59;48;2;62;62;62m▏ \e[0m\e[38;2;32;32;32m▏\e[0m \e[38;2;203;63;2m▄\e[38;2;147;45;1m▂\e[0m \e[7m\e[38;2;55;55;55m▆\e[38;2;60;60;60m▄\e[38;2;61;61;61m▂\e[38;2;60;60;60m▄\e[38;2;55;55;55m▆\e[0m \e[38;2;144;44;1m▂\e[38;2;202;62;2m▄\e[38;2;219;68;2m▆\e[38;2;231;72;3;48;2;226;70;2m┈\e[48;2;231;72;3m \e[48;2;225;70;2m▉\e[0m
|
|
\e[38;2;59;59;59;48;2;62;62;62m▏ \e[0m\e[38;2;32;32;32m▏\e[7m\e[38;2;121;37;1m▉\e[0m\e[38;2;0;0;0;48;2;231;72;3m \e[0m\e[38;2;221;68;2m▇\e[38;2;208;64;2m▅\e[38;2;212;66;2m▂\e[38;2;123;37;0m▁\e[38;2;211;65;2m▂\e[38;2;207;64;2m▅\e[38;2;220;68;2m▇\e[48;2;231;72;3m \e[38;2;231;72;3;48;2;225;70;2m┈\e[0m\e[7m\e[38;2;221;68;2m▂\e[0m\e[38;2;44;13;0;48;2;231;72;3m \e[38;2;231;72;3;48;2;225;70;2m▉\e[0m
|
|
\e[38;2;59;59;59;48;2;62;62;62m▏ \e[0m\e[38;2;32;32;32m▏\e[0m \e[7m\e[38;2;190;59;2m▅\e[38;2;216;67;2m▃\e[38;2;225;70;2m▁\e[0m\e[38;2;95;29;0;48;2;231;72;3m \e[38;2;231;72;3;48;2;230;71;2m┈\e[48;2;231;72;3m \e[0m\e[7m\e[38;2;225;70;2m▁\e[38;2;216;67;2m▃\e[38;2;191;59;2m▅\e[0m \e[38;2;0;0;0;48;2;231;72;3m \e[38;2;231;72;3;48;2;225;70;2m▉\e[0m
|
|
\e[38;2;59;59;59;48;2;62;62;62m▏ \e[0m\e[38;2;32;32;32m▏ \e[0m \e[7m\e[38;2;172;53;1m▆\e[38;2;213;66;2m▄\e[38;2;219;68;2m▂\e[38;2;213;66;2m▄\e[38;2;174;54;2m▆\e[0m \e[38;2;0;0;0m \e[0m \e[38;2;0;0;0;48;2;231;72;3m \e[38;2;231;72;3;48;2;225;70;2m▉\e[0m
|
|
\e[38;2;59;59;59;48;2;62;62;62m▏ \e[0m\e[38;2;32;32;32m▏ \e[0m \e[38;2;0;0;0;48;2;231;72;3m \e[38;2;231;72;3;48;2;225;70;2m▉\e[0m
|
|
\e[7m\e[38;2;52;52;52m▆\e[38;2;59;59;59m▄\e[38;2;61;61;61m▂\e[0m\e[38;2;31;31;31m▏ \e[0m \e[7m\e[38;2;228;71;2m▂\e[38;2;221;69;2m▄\e[38;2;196;60;2m▆\e[0m
|
|
EOF
|
|
)
|
|
|
|
|
|
TEXT=(
|
|
""
|
|
""
|
|
"${BOLD}ProxMenux${RESET}"
|
|
""
|
|
"${BOLD}${NEON_PURPLE_BLUE}An Interactive Menu for${RESET}"
|
|
"${BOLD}${NEON_PURPLE_BLUE}Proxmox VE management${RESET}"
|
|
""
|
|
""
|
|
""
|
|
""
|
|
)
|
|
|
|
|
|
mapfile -t logo_lines <<< "$LOGO"
|
|
|
|
for i in {0..9}; do
|
|
echo -e "${TAB}${logo_lines[i]} ${WHITE}│${RESET} ${TEXT[i]}"
|
|
done
|
|
echo -e
|
|
|
|
else
|
|
|
|
|
|
# Logo for terminal SSH
|
|
TEXT=(
|
|
""
|
|
""
|
|
""
|
|
""
|
|
"${BOLD}ProxMenux${RESET}"
|
|
""
|
|
"${BOLD}${NEON_PURPLE_BLUE}An Interactive Menu for${RESET}"
|
|
"${BOLD}${NEON_PURPLE_BLUE}Proxmox VE management${RESET}"
|
|
""
|
|
""
|
|
""
|
|
""
|
|
""
|
|
""
|
|
)
|
|
|
|
LOGO=(
|
|
"${DARK_GRAY}░░░░ ░░░░${RESET}"
|
|
"${DARK_GRAY}░░░░░░░ ░░░░░░ ${RESET}"
|
|
"${DARK_GRAY}░░░░░░░░░░░ ░░░░░░░ ${RESET}"
|
|
"${DARK_GRAY}░░░░ ░░░░░░ ░░░░░░ ${ORANGE}░░${RESET}"
|
|
"${DARK_GRAY}░░░░ ░░░░░░░ ${ORANGE}░░▒▒▒${RESET}"
|
|
"${DARK_GRAY}░░░░ ░░░ ${ORANGE}░▒▒▒▒▒▒▒${RESET}"
|
|
"${DARK_GRAY}░░░░ ${ORANGE}▒▒▒░ ░▒▒▒▒▒▒▒▒▒▒${RESET}"
|
|
"${DARK_GRAY}░░░░ ${ORANGE}░▒▒▒▒▒ ▒▒▒▒▒░░ ▒▒▒▒${RESET}"
|
|
"${DARK_GRAY}░░░░ ${ORANGE}░░▒▒▒▒▒▒▒░░ ▒▒▒▒${RESET}"
|
|
"${DARK_GRAY}░░░░ ${ORANGE}░░░ ▒▒▒▒${RESET}"
|
|
"${DARK_GRAY}░░░░ ${ORANGE}▒▒▒▒${RESET}"
|
|
"${DARK_GRAY}░░░░ ${ORANGE}▒▒▒░${RESET}"
|
|
"${DARK_GRAY} ░░ ${ORANGE}░░ ${RESET}"
|
|
)
|
|
|
|
for i in {0..12}; do
|
|
echo -e "${TAB}${LOGO[i]} │${RESET} ${TEXT[i]}"
|
|
done
|
|
echo -e
|
|
fi
|
|
|
|
}
|
|
|
|
# ==========================================================
|
|
|
|
|
|
|
|
|
|
|
|
cleanup_corrupted_files() {
|
|
if [ -f "$CONFIG_FILE" ] && ! jq empty "$CONFIG_FILE" >/dev/null 2>&1; then
|
|
echo "Cleaning up corrupted configuration file..."
|
|
rm -f "$CONFIG_FILE"
|
|
fi
|
|
if [ -f "$CACHE_FILE" ] && ! jq empty "$CACHE_FILE" >/dev/null 2>&1; then
|
|
echo "Cleaning up corrupted cache file..."
|
|
rm -f "$CACHE_FILE"
|
|
fi
|
|
}
|
|
|
|
# Cleanup function
|
|
cleanup() {
|
|
if [ -d "$TEMP_DIR" ]; then
|
|
rm -rf "$TEMP_DIR"
|
|
fi
|
|
}
|
|
|
|
# Set trap to ensure cleanup on exit
|
|
trap cleanup EXIT
|
|
|
|
|
|
# ==========================================================
|
|
check_existing_installation() {
|
|
local has_venv=false
|
|
local has_config=false
|
|
local has_language=false
|
|
local has_menu=false
|
|
|
|
if [ -f "$INSTALL_DIR/$MENU_SCRIPT" ]; then
|
|
has_menu=true
|
|
fi
|
|
|
|
if [ -d "$VENV_PATH" ] && [ -f "$VENV_PATH/bin/activate" ]; then
|
|
has_venv=true
|
|
fi
|
|
|
|
if [ -f "$CONFIG_FILE" ]; then
|
|
if jq empty "$CONFIG_FILE" >/dev/null 2>&1; then
|
|
has_config=true
|
|
local current_language=$(jq -r '.language // empty' "$CONFIG_FILE" 2>/dev/null)
|
|
if [[ -n "$current_language" && "$current_language" != "null" && "$current_language" != "empty" ]]; then
|
|
has_language=true
|
|
fi
|
|
else
|
|
echo "Warning: Corrupted config file detected, removing..."
|
|
rm -f "$CONFIG_FILE"
|
|
fi
|
|
fi
|
|
|
|
if [ "$has_venv" = true ] && [ "$has_language" = true ]; then
|
|
echo "translation"
|
|
elif [ "$has_menu" = true ] && [ "$has_venv" = false ]; then
|
|
echo "normal"
|
|
elif [ "$has_menu" = true ]; then
|
|
echo "unknown"
|
|
else
|
|
echo "none"
|
|
fi
|
|
}
|
|
|
|
uninstall_proxmenux() {
|
|
local install_type="$1"
|
|
local force_clean="$2"
|
|
|
|
if [ "$force_clean" != "force" ]; then
|
|
if ! whiptail --title "Uninstall ProxMenux" --yesno "Are you sure you want to uninstall ProxMenux?" 10 60; then
|
|
return 1
|
|
fi
|
|
fi
|
|
|
|
echo "Uninstalling ProxMenux..."
|
|
|
|
if systemctl is-active --quiet proxmenux-monitor.service; then
|
|
echo "Stopping ProxMenux Monitor service..."
|
|
systemctl stop proxmenux-monitor.service
|
|
fi
|
|
|
|
if systemctl is-enabled --quiet proxmenux-monitor.service 2>/dev/null; then
|
|
echo "Disabling ProxMenux Monitor service..."
|
|
systemctl disable proxmenux-monitor.service
|
|
fi
|
|
|
|
if [ -f "$MONITOR_SERVICE_FILE" ]; then
|
|
echo "Removing ProxMenux Monitor service file..."
|
|
rm -f "$MONITOR_SERVICE_FILE"
|
|
systemctl daemon-reload
|
|
fi
|
|
|
|
if [ -d "$MONITOR_INSTALL_DIR" ]; then
|
|
echo "Removing ProxMenux Monitor directory..."
|
|
rm -rf "$MONITOR_INSTALL_DIR"
|
|
fi
|
|
|
|
if [ -f "$VENV_PATH/bin/activate" ]; then
|
|
echo "Removing googletrans and virtual environment..."
|
|
source "$VENV_PATH/bin/activate"
|
|
pip uninstall -y googletrans >/dev/null 2>&1
|
|
deactivate
|
|
rm -rf "$VENV_PATH"
|
|
fi
|
|
|
|
if [ "$install_type" = "translation" ] && [ "$force_clean" != "force" ]; then
|
|
DEPS_TO_REMOVE=$(whiptail --title "Remove Translation Dependencies" --checklist \
|
|
"Select translation-specific dependencies to remove:" 15 60 3 \
|
|
"python3-venv" "Python virtual environment" OFF \
|
|
"python3-pip" "Python package installer" OFF \
|
|
"python3" "Python interpreter" OFF \
|
|
3>&1 1>&2 2>&3)
|
|
|
|
if [ -n "$DEPS_TO_REMOVE" ]; then
|
|
echo "Removing selected dependencies..."
|
|
read -r -a DEPS_ARRAY <<< "$(echo "$DEPS_TO_REMOVE" | tr -d '"')"
|
|
for dep in "${DEPS_ARRAY[@]}"; do
|
|
echo "Removing $dep..."
|
|
apt-mark auto "$dep" >/dev/null 2>&1
|
|
apt-get -y --purge autoremove "$dep" >/dev/null 2>&1
|
|
done
|
|
apt-get autoremove -y --purge >/dev/null 2>&1
|
|
fi
|
|
fi
|
|
|
|
rm -f "$INSTALL_DIR/$MENU_SCRIPT"
|
|
rm -rf "$BASE_DIR"
|
|
|
|
[ -f /root/.bashrc.bak ] && mv /root/.bashrc.bak /root/.bashrc
|
|
if [ -f /etc/motd.bak ]; then
|
|
mv /etc/motd.bak /etc/motd
|
|
else
|
|
sed -i '/This system is optimised by: ProxMenux/d' /etc/motd
|
|
fi
|
|
|
|
echo "ProxMenux has been uninstalled."
|
|
return 0
|
|
}
|
|
|
|
handle_installation_change() {
|
|
local current_type="$1"
|
|
local new_type="$2"
|
|
|
|
if [ "$current_type" = "$new_type" ]; then
|
|
return 0
|
|
fi
|
|
|
|
case "$current_type-$new_type" in
|
|
"translation-1"|"translation-normal")
|
|
if whiptail --title "Installation Type Change" \
|
|
--yesno "Switch from Translation to Normal Version?\n\nThis will remove translation components." 10 60; then
|
|
echo "Preparing for installation type change..."
|
|
uninstall_proxmenux "translation" "force" >/dev/null 2>&1
|
|
return 0
|
|
else
|
|
return 1
|
|
fi
|
|
;;
|
|
"normal-2"|"normal-translation")
|
|
if whiptail --title "Installation Type Change" \
|
|
--yesno "Switch from Normal to Translation Version?\n\nThis will add translation components." 10 60; then
|
|
return 0
|
|
else
|
|
return 1
|
|
fi
|
|
;;
|
|
*)
|
|
return 0
|
|
;;
|
|
esac
|
|
}
|
|
|
|
update_config() {
|
|
local component="$1"
|
|
local status="$2"
|
|
local timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
|
|
local tracked_components=("dialog" "curl" "jq" "git" "python3" "python3-venv" "python3-pip" "virtual_environment" "pip" "googletrans" "proxmenux_monitor")
|
|
|
|
if [[ " ${tracked_components[@]} " =~ " ${component} " ]]; then
|
|
mkdir -p "$(dirname "$CONFIG_FILE")"
|
|
|
|
if [ ! -f "$CONFIG_FILE" ] || ! jq empty "$CONFIG_FILE" >/dev/null 2>&1; then
|
|
echo '{}' > "$CONFIG_FILE"
|
|
fi
|
|
|
|
local tmp_file=$(mktemp)
|
|
if jq --arg comp "$component" --arg stat "$status" --arg time "$timestamp" \
|
|
'.[$comp] = {status: $stat, timestamp: $time}' "$CONFIG_FILE" > "$tmp_file" 2>/dev/null; then
|
|
mv "$tmp_file" "$CONFIG_FILE"
|
|
else
|
|
echo '{}' > "$CONFIG_FILE"
|
|
jq --arg comp "$component" --arg stat "$status" --arg time "$timestamp" \
|
|
'.[$comp] = {status: $stat, timestamp: $time}' "$CONFIG_FILE" > "$tmp_file" && mv "$tmp_file" "$CONFIG_FILE"
|
|
fi
|
|
|
|
[ -f "$tmp_file" ] && rm -f "$tmp_file"
|
|
fi
|
|
}
|
|
|
|
show_progress() {
|
|
local step="$1"
|
|
local total="$2"
|
|
local message="$3"
|
|
|
|
echo -e "\n${BOLD}${BL}${TAB}Installing ProxMenux: Step $step of $total${CL}"
|
|
echo
|
|
msg_info2 "$message"
|
|
}
|
|
|
|
select_language() {
|
|
if [ -f "$CONFIG_FILE" ] && jq empty "$CONFIG_FILE" >/dev/null 2>&1; then
|
|
local existing_language=$(jq -r '.language // empty' "$CONFIG_FILE" 2>/dev/null)
|
|
if [[ -n "$existing_language" && "$existing_language" != "null" && "$existing_language" != "empty" ]]; then
|
|
LANGUAGE="$existing_language"
|
|
msg_ok "Using existing language configuration: $LANGUAGE"
|
|
return 0
|
|
fi
|
|
fi
|
|
|
|
LANGUAGE=$(whiptail --title "Select Language" --menu "Choose a language for the menu:" 20 60 12 \
|
|
"en" "English (Recommended)" \
|
|
"es" "Spanish" \
|
|
"fr" "French" \
|
|
"de" "German" \
|
|
"it" "Italian" \
|
|
"pt" "Portuguese" 3>&1 1>&2 2>&3)
|
|
|
|
if [ -z "$LANGUAGE" ]; then
|
|
msg_error "No language selected. Exiting."
|
|
exit 1
|
|
fi
|
|
|
|
mkdir -p "$(dirname "$CONFIG_FILE")"
|
|
|
|
if [ ! -f "$CONFIG_FILE" ] || ! jq empty "$CONFIG_FILE" >/dev/null 2>&1; then
|
|
echo '{}' > "$CONFIG_FILE"
|
|
fi
|
|
|
|
local tmp_file=$(mktemp)
|
|
if jq --arg lang "$LANGUAGE" '. + {language: $lang}' "$CONFIG_FILE" > "$tmp_file" 2>/dev/null; then
|
|
mv "$tmp_file" "$CONFIG_FILE"
|
|
else
|
|
echo "{\"language\": \"$LANGUAGE\"}" > "$CONFIG_FILE"
|
|
fi
|
|
|
|
[ -f "$tmp_file" ] && rm -f "$tmp_file"
|
|
|
|
msg_ok "Language set to: $LANGUAGE"
|
|
}
|
|
|
|
# Show installation confirmation for new installations
|
|
show_installation_confirmation() {
|
|
local install_type="$1"
|
|
|
|
case "$install_type" in
|
|
"1")
|
|
if whiptail --title "ProxMenux - Normal Version Installation" \
|
|
--yesno "ProxMenux Normal Version will install:\n\n• dialog (interactive menus) - Official Debian package\n• curl (file downloads) - Official Debian package\n• jq (JSON processing) - Official Debian package\n• ProxMenux core files (/usr/local/share/proxmenux)\n• ProxMenux Monitor (Web dashboard on port 8008)\n\nThis is a lightweight installation with minimal dependencies.\n\nProceed with installation?" 20 70; then
|
|
return 0
|
|
else
|
|
return 1
|
|
fi
|
|
;;
|
|
"2")
|
|
if whiptail --title "ProxMenux - Translation Version Installation" \
|
|
--yesno "ProxMenux Translation Version will install:\n\n• dialog (interactive menus)\n• curl (file downloads)\n• jq (JSON processing)\n• python3 + python3-venv + python3-pip\n• Google Translate library (googletrans)\n• Virtual environment (/opt/googletrans-env)\n• Translation cache system\n• ProxMenux core files\n• ProxMenux Monitor (Web dashboard on port 8008)\n\nThis version requires more dependencies for translation support.\n\nProceed with installation?" 20 70; then
|
|
return 0
|
|
else
|
|
return 1
|
|
fi
|
|
;;
|
|
esac
|
|
}
|
|
|
|
get_server_ip() {
|
|
local ip
|
|
# Try to get the primary IP address
|
|
ip=$(ip route get 1.1.1.1 2>/dev/null | grep -oP 'src \K\S+')
|
|
|
|
if [ -z "$ip" ]; then
|
|
# Fallback: get first non-loopback IP
|
|
ip=$(hostname -I | awk '{print $1}')
|
|
fi
|
|
|
|
if [ -z "$ip" ]; then
|
|
# Last resort: use localhost
|
|
ip="localhost"
|
|
fi
|
|
|
|
echo "$ip"
|
|
}
|
|
|
|
detect_latest_appimage() {
|
|
local appimage_dir="$TEMP_DIR/AppImage"
|
|
|
|
if [ ! -d "$appimage_dir" ]; then
|
|
return 1
|
|
fi
|
|
|
|
local latest_appimage=$(find "$appimage_dir" -name "ProxMenux-*.AppImage" -type f | sort -V | tail -1)
|
|
|
|
if [ -z "$latest_appimage" ]; then
|
|
return 1
|
|
fi
|
|
|
|
echo "$latest_appimage"
|
|
return 0
|
|
}
|
|
|
|
get_appimage_version() {
|
|
local appimage_path="$1"
|
|
local filename=$(basename "$appimage_path")
|
|
|
|
# Match any dotted number sequence + optional pre-release suffix
|
|
# (e.g. "-beta"). The previous `[0-9]+\.[0-9]+\.[0-9]+` was hardcoded
|
|
# to three segments and dropped both the fourth segment AND the
|
|
# `-beta` suffix on a name like `ProxMenux-1.2.1.2-beta.AppImage`.
|
|
local version=$(echo "$filename" | grep -oP 'ProxMenux-\K[0-9]+(?:\.[0-9]+)+(?:-[A-Za-z0-9]+)?')
|
|
|
|
echo "$version"
|
|
}
|
|
|
|
# ── AppImage runtime extraction ────────────────────────────
|
|
# Extract the AppImage's squashfs to a stable directory and run AppRun
|
|
# directly. Avoids the FUSE mount under /tmp/.mount_ProxMe<random>, which
|
|
# trips Wazuh rule 521 / rkhunter "Possible kernel level rootkit" alerts
|
|
# (issue #101) — those scanners flag any directory that appears in
|
|
# readdir() but is hidden from lstat(), which is exactly what AppImage's
|
|
# FUSE mount layer looks like to them. Running from a plain extracted
|
|
# directory has the same files but no FUSE indirection, so the false
|
|
# positive disappears.
|
|
extract_appimage_to_runtime_dir() {
|
|
local appimage_path="$1"
|
|
local target_runtime_dir="$2"
|
|
local tmp_extract_dir
|
|
tmp_extract_dir=$(mktemp -d /tmp/proxmenux-extract.XXXXXX) || return 1
|
|
|
|
msg_info "Extracting AppImage runtime to ${target_runtime_dir}..."
|
|
|
|
if ! ( cd "$tmp_extract_dir" && "$appimage_path" --appimage-extract >/dev/null 2>&1 ); then
|
|
msg_error "Failed to extract AppImage."
|
|
rm -rf "$tmp_extract_dir"
|
|
return 1
|
|
fi
|
|
|
|
if [ ! -x "$tmp_extract_dir/squashfs-root/AppRun" ]; then
|
|
msg_error "Extracted AppImage missing AppRun."
|
|
rm -rf "$tmp_extract_dir"
|
|
return 1
|
|
fi
|
|
|
|
rm -rf "${target_runtime_dir}.new"
|
|
mv "$tmp_extract_dir/squashfs-root" "${target_runtime_dir}.new"
|
|
rm -rf "$tmp_extract_dir"
|
|
|
|
if [ -d "$target_runtime_dir" ]; then
|
|
rm -rf "${target_runtime_dir}.old"
|
|
mv "$target_runtime_dir" "${target_runtime_dir}.old"
|
|
fi
|
|
mv "${target_runtime_dir}.new" "$target_runtime_dir"
|
|
rm -rf "${target_runtime_dir}.old"
|
|
|
|
rm -f "$appimage_path"
|
|
|
|
msg_ok "AppImage runtime extracted (no FUSE mount; bypasses Wazuh rule 521)."
|
|
return 0
|
|
}
|
|
|
|
install_proxmenux_monitor() {
|
|
local appimage_source=$(detect_latest_appimage)
|
|
|
|
if [ -z "$appimage_source" ] || [ ! -f "$appimage_source" ]; then
|
|
msg_error "ProxMenux Monitor AppImage not found in $TEMP_DIR/AppImage/"
|
|
msg_warn "Please ensure the AppImage directory exists with ProxMenux-*.AppImage files."
|
|
update_config "proxmenux_monitor" "appimage_not_found"
|
|
return 1
|
|
fi
|
|
|
|
local appimage_version=$(get_appimage_version "$appimage_source")
|
|
|
|
if systemctl is-active --quiet proxmenux-monitor.service; then
|
|
systemctl stop proxmenux-monitor.service
|
|
fi
|
|
|
|
local service_exists=false
|
|
if [ -f "$MONITOR_SERVICE_FILE" ]; then
|
|
service_exists=true
|
|
fi
|
|
|
|
local sha256_file="$TEMP_DIR/AppImage/ProxMenux-Monitor.AppImage.sha256"
|
|
|
|
if [ -f "$sha256_file" ]; then
|
|
msg_info "Verifying AppImage integrity..."
|
|
local expected_hash=$(cat "$sha256_file" | grep -Eo '^[a-f0-9]+' | tr -d '\n')
|
|
local actual_hash=$(sha256sum "$appimage_source" | awk '{print $1}')
|
|
|
|
if [ "$expected_hash" != "$actual_hash" ]; then
|
|
msg_error "SHA256 verification failed! AppImage may be corrupted."
|
|
return 1
|
|
fi
|
|
msg_ok "SHA256 verification passed."
|
|
else
|
|
msg_warn "SHA256 checksum not available. Skipping verification."
|
|
fi
|
|
|
|
mkdir -p "$MONITOR_INSTALL_DIR"
|
|
|
|
local target_path="$MONITOR_INSTALL_DIR/ProxMenux-Monitor.AppImage"
|
|
cp "$appimage_source" "$target_path"
|
|
chmod +x "$target_path"
|
|
|
|
if ! extract_appimage_to_runtime_dir "$target_path" "$MONITOR_RUNTIME_DIR"; then
|
|
update_config "proxmenux_monitor" "extract_failed"
|
|
return 1
|
|
fi
|
|
|
|
msg_ok "ProxMenux Monitor v$appimage_version installed."
|
|
|
|
if [ "$service_exists" = false ]; then
|
|
return 0 # New installation - service needs to be created
|
|
else
|
|
# The v1.2.2 install layout extracts the AppImage into
|
|
# MONITOR_RUNTIME_DIR/ and runs AppRun out of that directory
|
|
# (`extract_appimage_to_runtime_dir` above), so the unit must
|
|
# point at AppRun — not at the bare AppImage. Existing users
|
|
# updating from v1.2.1.x stable still have a unit whose
|
|
# ExecStart targets `/usr/local/share/proxmenux/ProxMenux-Monitor.AppImage`
|
|
# which was fine when the AppImage was FUSE-mounted but breaks
|
|
# under PVE 9.x / Debian 13 (status=203/EXEC, GitHub issue #222).
|
|
# Rewrite the unit on every update — idempotent for users
|
|
# whose unit is already correct.
|
|
_proxmenux_rewrite_monitor_unit_for_apprun
|
|
|
|
systemctl start proxmenux-monitor.service
|
|
sleep 2
|
|
|
|
if systemctl is-active --quiet proxmenux-monitor.service; then
|
|
|
|
update_config "proxmenux_monitor" "updated"
|
|
return 2 # Update successful
|
|
else
|
|
msg_warn "Service failed to restart. Check: journalctl -u proxmenux-monitor"
|
|
update_config "proxmenux_monitor" "failed"
|
|
return 1
|
|
fi
|
|
fi
|
|
}
|
|
|
|
# Idempotent rewriter of the proxmenux-monitor unit file. Used by the
|
|
# update path in `install_proxmenux_monitor` so that existing
|
|
# installations updated to v1.2.2+ get their ExecStart corrected to
|
|
# point at the extracted AppRun even when the unit already exists.
|
|
# Mirrors `create_monitor_service`'s unit body so both code paths
|
|
# converge on the same file content. Returns 0 always; failures are
|
|
# logged so the surrounding flow can still attempt the start and
|
|
# report a more accurate failure to the user.
|
|
_proxmenux_rewrite_monitor_unit_for_apprun() {
|
|
local exec_path="$MONITOR_RUNTIME_DIR/AppRun"
|
|
|
|
if [ -f "$TEMP_DIR/systemd/proxmenux-monitor.service" ]; then
|
|
sed "s|ExecStart=.*|ExecStart=$exec_path|g" \
|
|
"$TEMP_DIR/systemd/proxmenux-monitor.service" > "$MONITOR_SERVICE_FILE"
|
|
else
|
|
cat > "$MONITOR_SERVICE_FILE" << EOF
|
|
[Unit]
|
|
Description=ProxMenux Monitor - Web Dashboard
|
|
After=network.target
|
|
|
|
[Service]
|
|
Type=simple
|
|
User=root
|
|
WorkingDirectory=$MONITOR_INSTALL_DIR
|
|
ExecStart=$exec_path
|
|
Restart=on-failure
|
|
RestartSec=10
|
|
Environment="PORT=$MONITOR_PORT"
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
EOF
|
|
fi
|
|
|
|
systemctl daemon-reload
|
|
return 0
|
|
}
|
|
|
|
create_monitor_service() {
|
|
|
|
local exec_path="$MONITOR_RUNTIME_DIR/AppRun"
|
|
|
|
if [ -f "$TEMP_DIR/systemd/proxmenux-monitor.service" ]; then
|
|
sed "s|ExecStart=.*|ExecStart=$exec_path|g" \
|
|
"$TEMP_DIR/systemd/proxmenux-monitor.service" > "$MONITOR_SERVICE_FILE"
|
|
msg_ok "Using service file from repository."
|
|
else
|
|
cat > "$MONITOR_SERVICE_FILE" << EOF
|
|
[Unit]
|
|
Description=ProxMenux Monitor - Web Dashboard
|
|
After=network.target
|
|
|
|
[Service]
|
|
Type=simple
|
|
User=root
|
|
WorkingDirectory=$MONITOR_INSTALL_DIR
|
|
ExecStart=$exec_path
|
|
Restart=on-failure
|
|
RestartSec=10
|
|
Environment="PORT=$MONITOR_PORT"
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
EOF
|
|
msg_ok "Created default service file."
|
|
fi
|
|
|
|
systemctl daemon-reload
|
|
systemctl enable proxmenux-monitor.service > /dev/null 2>&1
|
|
systemctl start proxmenux-monitor.service > /dev/null 2>&1
|
|
|
|
sleep 3
|
|
|
|
if systemctl is-active --quiet proxmenux-monitor.service; then
|
|
msg_ok "ProxMenux Monitor service started successfully."
|
|
update_config "proxmenux_monitor" "installed"
|
|
return 0
|
|
else
|
|
msg_warn "ProxMenux Monitor service failed to start."
|
|
msg_info2 "Check logs with: journalctl -u proxmenux-monitor -n 20"
|
|
msg_info2 "Check status with: systemctl status proxmenux-monitor"
|
|
update_config "proxmenux_monitor" "failed"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
install_normal_version() {
|
|
local total_steps=5
|
|
local current_step=1
|
|
|
|
show_progress $current_step $total_steps "Installing basic dependencies."
|
|
|
|
if ! command -v jq > /dev/null 2>&1; then
|
|
apt-get update > /dev/null 2>&1
|
|
|
|
if apt-get install -y jq > /dev/null 2>&1 && command -v jq > /dev/null 2>&1; then
|
|
update_config "jq" "installed"
|
|
else
|
|
local jq_url="https://github.com/jqlang/jq/releases/download/jq-1.7.1/jq-linux-amd64"
|
|
if wget -q -O /usr/local/bin/jq "$jq_url" 2>/dev/null && chmod +x /usr/local/bin/jq; then
|
|
if command -v jq > /dev/null 2>&1; then
|
|
update_config "jq" "installed_from_github"
|
|
else
|
|
msg_error "Failed to install jq. Please install it manually."
|
|
update_config "jq" "failed"
|
|
return 1
|
|
fi
|
|
else
|
|
msg_error "Failed to install jq from both APT and GitHub. Please install it manually."
|
|
update_config "jq" "failed"
|
|
return 1
|
|
fi
|
|
fi
|
|
else
|
|
update_config "jq" "already_installed"
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
BASIC_DEPS=("dialog" "curl" "git")
|
|
|
|
if [ -z "${APT_UPDATED:-}" ]; then
|
|
apt-get update -y > /dev/null 2>&1 || true
|
|
APT_UPDATED=1
|
|
fi
|
|
|
|
for pkg in "${BASIC_DEPS[@]}"; do
|
|
# Strict per-package check — see comment in install_translation_version().
|
|
if ! dpkg-query -W -f='${Status}' "$pkg" 2>/dev/null | grep -q "ok installed"; then
|
|
if apt-get install -y "$pkg" > /dev/null 2>&1; then
|
|
update_config "$pkg" "installed"
|
|
else
|
|
msg_error "Failed to install $pkg. Please install it manually."
|
|
update_config "$pkg" "failed"
|
|
return 1
|
|
fi
|
|
else
|
|
update_config "$pkg" "already_installed"
|
|
fi
|
|
done
|
|
|
|
|
|
if ! command -v git > /dev/null 2>&1; then
|
|
msg_info "Installing git (required to clone the ProxMenux repository)."
|
|
|
|
|
|
if [ -z "${APT_UPDATED:-}" ]; then
|
|
apt-get update -y > /dev/null 2>&1 || true
|
|
APT_UPDATED=1
|
|
fi
|
|
|
|
if ! apt-get install -y git > /dev/null 2>&1; then
|
|
msg_error "Failed to install git. Please run 'apt-get install git' manually and rerun the installer."
|
|
update_config "git" "failed"
|
|
return 1
|
|
fi
|
|
|
|
|
|
if ! command -v git > /dev/null 2>&1; then
|
|
msg_error "Git is still not available after installation. Aborting to avoid a broken setup."
|
|
update_config "git" "failed"
|
|
return 1
|
|
fi
|
|
|
|
update_config "git" "installed"
|
|
else
|
|
update_config "git" "already_installed"
|
|
fi
|
|
|
|
msg_ok "jq, dialog, curl and git installed successfully."
|
|
|
|
|
|
|
|
|
|
|
|
|
|
((current_step++))
|
|
|
|
show_progress $current_step $total_steps "Install ProxMenux repository"
|
|
msg_info "Cloning ProxMenux repositoryy."
|
|
if ! git clone --depth 1 "$REPO_URL" "$TEMP_DIR" 2>/dev/null; then
|
|
msg_error "Failed to clone repository from $REPO_URL"
|
|
exit 1
|
|
fi
|
|
|
|
msg_ok "Repository cloned successfully."
|
|
|
|
cd "$TEMP_DIR"
|
|
|
|
((current_step++))
|
|
|
|
show_progress $current_step $total_steps "Creating directories and configuration"
|
|
|
|
mkdir -p "$BASE_DIR"
|
|
mkdir -p "$INSTALL_DIR"
|
|
|
|
if [ ! -f "$CONFIG_FILE" ]; then
|
|
echo '{}' > "$CONFIG_FILE"
|
|
fi
|
|
|
|
msg_ok "Directories and configuration created."
|
|
((current_step++))
|
|
|
|
show_progress $current_step $total_steps "Copying necessary files"
|
|
|
|
cp "./scripts/utils.sh" "$UTILS_FILE"
|
|
cp "./menu" "$INSTALL_DIR/$MENU_SCRIPT"
|
|
cp "./version.txt" "$LOCAL_VERSION_FILE"
|
|
cp "./install_proxmenux.sh" "$BASE_DIR/install_proxmenux.sh"
|
|
|
|
# A user that previously rode the beta train and then switched back
|
|
# to stable would still have a leftover beta_version.txt under
|
|
# $BASE_DIR, which makes the `menu` update check (check_updates_beta)
|
|
# offer a "Beta update available" prompt on top of the legitimate
|
|
# stable one. Clearing the marker on every stable install/update
|
|
# keeps the stable install honestly stable — if the user opts into
|
|
# the beta program again, the beta installer will recreate the file.
|
|
rm -f "$BASE_DIR/beta_version.txt"
|
|
|
|
# Wipe the scripts tree before copying so any file removed upstream
|
|
# (renamed, consolidated, deprecated) disappears from the user install.
|
|
# Only $BASE_DIR/scripts/ is cleared; config.json, cache.json,
|
|
# components_status.json, version.txt, monitor.db, smart/, oci/ and
|
|
# the AppImage live outside this path and are preserved.
|
|
rm -rf "$BASE_DIR/scripts"
|
|
mkdir -p "$BASE_DIR/scripts"
|
|
cp -r "./scripts/"* "$BASE_DIR/scripts/"
|
|
# Only .sh files need the executable bit. Applying +x recursively would
|
|
# also flag README.md, .json, .py etc. as executable for no reason.
|
|
find "$BASE_DIR/scripts" -type f -name '*.sh' -exec chmod +x {} +
|
|
chmod +x "$BASE_DIR/install_proxmenux.sh"
|
|
msg_ok "Necessary files created."
|
|
|
|
chmod +x "$INSTALL_DIR/$MENU_SCRIPT"
|
|
|
|
((current_step++))
|
|
show_progress $current_step $total_steps "Installing ProxMenux Monitor"
|
|
|
|
install_proxmenux_monitor
|
|
local monitor_status=$?
|
|
|
|
if [ $monitor_status -eq 0 ]; then
|
|
create_monitor_service
|
|
fi
|
|
|
|
msg_ok "ProxMenux Normal Version installation completed successfully."
|
|
}
|
|
|
|
install_translation_version() {
|
|
local total_steps=5
|
|
local current_step=1
|
|
|
|
show_progress $current_step $total_steps "Language selection"
|
|
select_language
|
|
((current_step++))
|
|
|
|
show_progress $current_step $total_steps "Installing system dependencies"
|
|
|
|
if ! command -v jq > /dev/null 2>&1; then
|
|
apt-get update > /dev/null 2>&1
|
|
|
|
if apt-get install -y jq > /dev/null 2>&1 && command -v jq > /dev/null 2>&1; then
|
|
update_config "jq" "installed"
|
|
else
|
|
local jq_url="https://github.com/jqlang/jq/releases/download/jq-1.7.1/jq-linux-amd64"
|
|
if wget -q -O /usr/local/bin/jq "$jq_url" 2>/dev/null && chmod +x /usr/local/bin/jq; then
|
|
if command -v jq > /dev/null 2>&1; then
|
|
update_config "jq" "installed_from_github"
|
|
else
|
|
msg_error "Failed to install jq. Please install it manually."
|
|
update_config "jq" "failed"
|
|
return 1
|
|
fi
|
|
else
|
|
msg_error "Failed to install jq from both APT and GitHub. Please install it manually."
|
|
update_config "jq" "failed"
|
|
return 1
|
|
fi
|
|
fi
|
|
else
|
|
update_config "jq" "already_installed"
|
|
fi
|
|
|
|
DEPS=("dialog" "curl" "git" "python3" "python3-venv" "python3-pip")
|
|
for pkg in "${DEPS[@]}"; do
|
|
# `dpkg -l | grep -qw "$pkg"` treats `-` as a word boundary, so a
|
|
# query for `python3` would falsely match `python3-pip` and skip
|
|
# the real `python3` install. `dpkg-query -W -f='${Status}'` asks
|
|
# for the EXACT package and reports "install ok installed" only
|
|
# when truly present. Issue #205 traced back here.
|
|
if ! dpkg-query -W -f='${Status}' "$pkg" 2>/dev/null | grep -q "ok installed"; then
|
|
if apt-get install -y "$pkg" > /dev/null 2>&1; then
|
|
update_config "$pkg" "installed"
|
|
else
|
|
msg_error "Failed to install $pkg. Please install it manually."
|
|
update_config "$pkg" "failed"
|
|
return 1
|
|
fi
|
|
else
|
|
update_config "$pkg" "already_installed"
|
|
fi
|
|
done
|
|
|
|
msg_ok "jq, dialog, curl, git, python3, python3-venv and python3-pip installed successfully."
|
|
|
|
((current_step++))
|
|
|
|
show_progress $current_step $total_steps "Setting up translation environment"
|
|
|
|
if [ ! -d "$VENV_PATH" ] || [ ! -f "$VENV_PATH/bin/activate" ]; then
|
|
python3 -m venv --system-site-packages "$VENV_PATH" > /dev/null 2>&1
|
|
if [ ! -f "$VENV_PATH/bin/activate" ]; then
|
|
msg_error "Failed to create virtual environment. Please check your Python installation."
|
|
update_config "virtual_environment" "failed"
|
|
return 1
|
|
else
|
|
update_config "virtual_environment" "created"
|
|
fi
|
|
else
|
|
update_config "virtual_environment" "already_exists"
|
|
fi
|
|
|
|
source "$VENV_PATH/bin/activate"
|
|
|
|
if pip install --upgrade pip > /dev/null 2>&1; then
|
|
update_config "pip" "upgraded"
|
|
else
|
|
msg_error "Failed to upgrade pip."
|
|
update_config "pip" "upgrade_failed"
|
|
return 1
|
|
fi
|
|
|
|
if pip install --break-system-packages --no-cache-dir googletrans==4.0.0-rc1 > /dev/null 2>&1; then
|
|
update_config "googletrans" "installed"
|
|
else
|
|
msg_error "Failed to install googletrans. Please check your internet connection."
|
|
update_config "googletrans" "failed"
|
|
deactivate
|
|
return 1
|
|
fi
|
|
|
|
deactivate
|
|
|
|
show_progress $current_step $total_steps "Cloning ProxMenux repository"
|
|
if ! git clone --depth 1 "$REPO_URL" "$TEMP_DIR" 2>/dev/null; then
|
|
msg_error "Failed to clone repository from $REPO_URL"
|
|
exit 1
|
|
fi
|
|
msg_ok "Repository cloned successfully."
|
|
|
|
cd "$TEMP_DIR"
|
|
|
|
((current_step++))
|
|
|
|
show_progress $current_step $total_steps "Copying necessary files"
|
|
|
|
mkdir -p "$BASE_DIR"
|
|
mkdir -p "$INSTALL_DIR"
|
|
|
|
cp "./json/cache.json" "$CACHE_FILE"
|
|
msg_ok "Cache file copied with translations."
|
|
|
|
cp "./scripts/utils.sh" "$UTILS_FILE"
|
|
cp "./menu" "$INSTALL_DIR/$MENU_SCRIPT"
|
|
cp "./version.txt" "$LOCAL_VERSION_FILE"
|
|
cp "./install_proxmenux.sh" "$BASE_DIR/install_proxmenux.sh"
|
|
|
|
# Clear any leftover beta_version.txt — see the equivalent block
|
|
# in the update path above for the rationale (in short: prevents
|
|
# the menu from offering a phantom "Beta update available" after a
|
|
# user has switched back to the stable channel).
|
|
rm -f "$BASE_DIR/beta_version.txt"
|
|
|
|
mkdir -p "$BASE_DIR/scripts"
|
|
cp -r "./scripts/"* "$BASE_DIR/scripts/"
|
|
chmod -R +x "$BASE_DIR/scripts/"
|
|
chmod +x "$BASE_DIR/install_proxmenux.sh"
|
|
msg_ok "Necessary files created."
|
|
|
|
chmod +x "$INSTALL_DIR/$MENU_SCRIPT"
|
|
|
|
((current_step++))
|
|
show_progress $current_step $total_steps "Installing ProxMenux Monitor"
|
|
|
|
install_proxmenux_monitor
|
|
local monitor_status=$?
|
|
|
|
if [ $monitor_status -eq 0 ]; then
|
|
create_monitor_service
|
|
elif [ $monitor_status -eq 2 ]; then
|
|
msg_ok "ProxMenux Monitor updated successfully."
|
|
fi
|
|
|
|
msg_ok "ProxMenux Translation Version installation completed successfully."
|
|
}
|
|
|
|
show_installation_options() {
|
|
local current_install_type
|
|
current_install_type=$(check_existing_installation)
|
|
local pve_version
|
|
pve_version=$(pveversion 2>/dev/null | grep -oP 'pve-manager/\K[0-9]+' | head -1)
|
|
|
|
local menu_title="ProxMenux Installation"
|
|
local menu_text="Choose installation type:"
|
|
|
|
if [ "$current_install_type" != "none" ]; then
|
|
case "$current_install_type" in
|
|
"translation")
|
|
menu_title="ProxMenux Update - Translation Version Detected"
|
|
;;
|
|
"normal")
|
|
menu_title="ProxMenux Update - Normal Version Detected"
|
|
;;
|
|
"unknown")
|
|
menu_title="ProxMenux Update - Existing Installation Detected"
|
|
;;
|
|
esac
|
|
fi
|
|
|
|
if [[ "$pve_version" -ge 9 ]]; then
|
|
INSTALL_TYPE=$(whiptail --backtitle "ProxMenux" --title "$menu_title" --menu "\n$menu_text" 14 70 2 \
|
|
"1" "Normal Version (English only)" 3>&1 1>&2 2>&3)
|
|
|
|
if [ -z "$INSTALL_TYPE" ]; then
|
|
show_proxmenux_logo
|
|
msg_warn "Installation cancelled."
|
|
exit 1
|
|
fi
|
|
else
|
|
INSTALL_TYPE=$(whiptail --backtitle "ProxMenux" --title "$menu_title" --menu "\n$menu_text" 14 70 2 \
|
|
"1" "Normal Version (English only)" \
|
|
"2" "Translation Version (Multi-language support)" 3>&1 1>&2 2>&3)
|
|
|
|
if [ -z "$INSTALL_TYPE" ]; then
|
|
show_proxmenux_logo
|
|
msg_warn "Installation cancelled."
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
if [ -z "$INSTALL_TYPE" ]; then
|
|
show_proxmenux_logo
|
|
msg_warn "Installation cancelled."
|
|
exit 1
|
|
fi
|
|
|
|
if [ "$current_install_type" = "none" ]; then
|
|
if ! show_installation_confirmation "$INSTALL_TYPE"; then
|
|
show_proxmenux_logo
|
|
msg_warn "Installation cancelled."
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
if ! handle_installation_change "$current_install_type" "$INSTALL_TYPE"; then
|
|
show_proxmenux_logo
|
|
msg_warn "Installation cancelled."
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
install_proxmenux() {
|
|
show_installation_options
|
|
|
|
case "$INSTALL_TYPE" in
|
|
"1")
|
|
show_proxmenux_logo
|
|
msg_title "Installing ProxMenux - Normal Version"
|
|
install_normal_version
|
|
;;
|
|
"2")
|
|
show_proxmenux_logo
|
|
msg_title "Installing ProxMenux - Translation Version"
|
|
install_translation_version
|
|
;;
|
|
*)
|
|
msg_error "Invalid option selected."
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
if [[ -f "$UTILS_FILE" ]]; then
|
|
source "$UTILS_FILE"
|
|
fi
|
|
|
|
# ── Legacy gpu-guard hookscript auto-cleanup ──────────────
|
|
# Previous ProxMenux versions attached a hookscript to VMs/LXCs with GPU
|
|
# passthrough; that reference in the guest .conf broke backup/restore to
|
|
# hosts without the snippet. The hookscript system has been removed.
|
|
# This silently purges any leftover references and the snippet file.
|
|
# Idempotent: does nothing on hosts that never had the legacy hook.
|
|
if [ -x "$LOCAL_SCRIPTS/global/cleanup_gpu_hookscripts.sh" ]; then
|
|
bash "$LOCAL_SCRIPTS/global/cleanup_gpu_hookscripts.sh" || true
|
|
fi
|
|
|
|
msg_title "ProxMenux has been installed successfully"
|
|
|
|
if systemctl is-active --quiet proxmenux-monitor.service; then
|
|
local server_ip=$(get_server_ip)
|
|
echo -e "${GN}🌐 ProxMenux Monitor activated${CL}: ${BL}http://${server_ip}:${MONITOR_PORT}${CL}"
|
|
echo
|
|
fi
|
|
|
|
echo -ne "${GN}"
|
|
type_text "To run ProxMenux, simply execute this command in the console or terminal:"
|
|
echo -e "${YWB} menu${CL}"
|
|
echo
|
|
# -------
|
|
exit 0
|
|
}
|
|
|
|
if [ "$(id -u)" -ne 0 ]; then
|
|
msg_error "This script must be run as root."
|
|
exit 1
|
|
fi
|
|
|
|
cleanup_corrupted_files
|
|
install_proxmenux
|