mirror of
https://github.com/MCSManager/Script.git
synced 2025-11-28 07:20:24 +08:00
1360 lines
No EOL
40 KiB
Bash
1360 lines
No EOL
40 KiB
Bash
#!/bin/bash
|
|
# Official MCSManager installation script.
|
|
# This script installs or updates the MCSManager Web and/or Daemon to the latest version.
|
|
# ------------------------------------------------------------------------------
|
|
# Supported Linux distributions:
|
|
# This script supports the following mainstream Linux distributions:
|
|
# - Ubuntu: 18.04, 20.04, 22.04, 24.04
|
|
# - Debian: 10, 11, 12, 13
|
|
# - CentOS: 7, 8 Stream, 9 Stream, 10 Stream
|
|
# - RHEL: 7, 8, 9, 10
|
|
# - Arch Linux: Support planned (TBD)
|
|
# ------------------------------------------------------------------------------
|
|
|
|
# Target installation directory (can be overridden with --install-dir)
|
|
install_dir="/opt/mcsmanager"
|
|
|
|
# Primary download URL bas. Full package URL = download_base_url + package_name
|
|
download_base_url="https://github.com/MCSManager/MCSManager/releases/latest/download/"
|
|
|
|
# Fallback download URL (can also be a local directory or mirror)
|
|
download_fallback_url="https://download.mcsmanager.com/mcsmanager_linux_release.tar.gz"
|
|
|
|
# Name of the release package to download/detect
|
|
package_name="mcsmanager_linux_release.tar.gz"
|
|
|
|
# Node.js version to be installed
|
|
# Keep the leading "v"
|
|
node_version="v20.12.2"
|
|
|
|
# Node download base URL - primary
|
|
node_download_url_base="https://nodejs.org/dist/"
|
|
|
|
# Node download URL - fallback.
|
|
# This is the URL points directly to the file, not the base. This can also be a local absolute path.
|
|
# Only supports https:// or http:// for web locations.
|
|
node_download_fallback=""
|
|
|
|
# Node.js installation path (defaults to the MCSManager installation path. Can be overridden with --node-install-dir)
|
|
node_install_dir="$install_dir"
|
|
|
|
# Temp dir for file extraction
|
|
tmp_dir="/tmp"
|
|
|
|
# Bypass installed user permission check, override by --force-permission
|
|
force_permission=false
|
|
|
|
|
|
# --------------- Global Variables ---------------#
|
|
# DO NOT MODIFY #
|
|
|
|
|
|
# Component installation options.
|
|
# For fresh installs, both daemon and web components are installed by default.
|
|
# For updates, behavior depends on detected existing components.
|
|
# Can be overridden with --install daemon/web/all
|
|
install_daemon=true
|
|
install_web=true
|
|
|
|
# Install MCSM as (default: root).
|
|
# To install as a general user (e.g., "mcsm"), use the --user option: --user mcsm
|
|
# To ensure compatibility, only user mcsm is supported.
|
|
install_user="root"
|
|
# Installed user, for permission check
|
|
web_installed=false
|
|
daemon_installed=false
|
|
web_installed_user=""
|
|
daemon_installed_user=""
|
|
|
|
# Service file locations
|
|
# the final dir = systemd_file + {web/daemon} + ".service"
|
|
systemd_file="/etc/systemd/system/mcsm-"
|
|
# Optional: Override the default installation source file.
|
|
# If --install-source is specified, the installer will use the provided
|
|
# "mcsmanager_linux_release.tar.gz" file instead of downloading it.
|
|
# Only support local absolute path.
|
|
install_source_path=""
|
|
|
|
# temp path for extracted file(s)
|
|
install_tmp_dir="/opt/mcsmanager/mcsm_abcd"
|
|
|
|
# dir name for data dir backup
|
|
# e.g. /opt/mcsmanager/daemon/data -> /opt/mcsmanager/data_bak_data
|
|
# only valid for when during an update
|
|
backup_prefix="data_bak_"
|
|
|
|
# System architecture (detected automatically)
|
|
arch=""
|
|
version=""
|
|
distro=""
|
|
|
|
|
|
|
|
# Supported OS versions (map-style structure)
|
|
# Format: supported_os["distro_name"]="version1 version2 version3 ..."
|
|
declare -A supported_os
|
|
supported_os["Ubuntu"]="18 20 22 24"
|
|
supported_os["Debian"]="10 11 12 13"
|
|
supported_os["CentOS"]="7 8 8-stream 9-stream 10-stream"
|
|
supported_os["RHEL"]="7 8 9 10"
|
|
supported_os["Arch"]="rolling"
|
|
|
|
# Required system commands for installation
|
|
# These will be checked before logic process
|
|
required_commands=(
|
|
chmod
|
|
chown
|
|
wget
|
|
tar
|
|
stat
|
|
useradd
|
|
usermod
|
|
date
|
|
)
|
|
|
|
# Node.js related sections
|
|
# Enable strict version checking (exact match)
|
|
# enabled -> strict requriement for defined node version
|
|
# false -> newer version allowed
|
|
# Older version is NEVER allowed
|
|
strict_node_version_check=true
|
|
|
|
# Will be set based on actual node status
|
|
install_node=true
|
|
# Remove leading "v" from defined version
|
|
required_node_ver="${node_version#v}"
|
|
|
|
# Holds absolute path for node & npm
|
|
node_bin_path=""
|
|
npm_bin_path=""
|
|
# Hold Node.js arch name, e.g. x86_64 -> x64
|
|
node_arch=""
|
|
# Hold Node.js intallation path, e.g. ${node_install_dir}/node-${node_version}-linux-${arch}
|
|
node_path=""
|
|
|
|
# For installation result
|
|
daemon_key=""
|
|
daemon_port=""
|
|
web_port=""
|
|
daemon_key_config_subpath="data/Config/global.json"
|
|
web_port_config_subpath="data/SystemConfig/config.json"
|
|
|
|
# Terminal color & style related
|
|
# Default to false, auto check later
|
|
SUPPORTS_COLOR=false
|
|
SUPPORTS_STYLE=false
|
|
# Declare ANSI reset
|
|
RESET="\033[0m"
|
|
|
|
# Foreground colors
|
|
declare -A FG_COLORS=(
|
|
[black]="\033[0;30m"
|
|
[red]="\033[0;31m"
|
|
[green]="\033[0;32m"
|
|
[yellow]="\033[0;33m"
|
|
[blue]="\033[0;34m"
|
|
[magenta]="\033[0;35m"
|
|
[cyan]="\033[0;36m"
|
|
[white]="\033[0;37m"
|
|
)
|
|
|
|
# Font styles
|
|
declare -A STYLES=(
|
|
[bold]="\033[1m"
|
|
[underline]="\033[4m"
|
|
[italic]="\033[3m" # Often ignored
|
|
[clear_line]="\r\033[2K"
|
|
[strikethrough]="\033[9m"
|
|
)
|
|
|
|
|
|
### Helper Functions
|
|
# Execution wrapper, avoid unexpected crashes.
|
|
safe_run() {
|
|
local func="$1"
|
|
local err_msg="$2"
|
|
shift 2
|
|
|
|
if ! "$func" "$@"; then
|
|
echo "Error: $err_msg"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
# Function to ensure the script is run as root
|
|
check_root() {
|
|
# Using Bash's built-in EUID variable
|
|
if [ -n "$EUID" ]; then
|
|
if [ "$EUID" -ne 0 ]; then
|
|
cprint red "Error: This script must be run as root. Please use sudo or switch to the root user."
|
|
exit 1
|
|
fi
|
|
else
|
|
# Fallback to using id -u if EUID is unavailable (e.g., non-Bash shell or misconfigured environment)
|
|
if [ "$(id -u)" -ne 0 ]; then
|
|
cprint red "Error: This script must be run as root. Please use sudo or switch to the root user."
|
|
exit 1
|
|
fi
|
|
fi
|
|
}
|
|
|
|
# Function to check whether current terminal support color & style
|
|
detect_terminal_capabilities() {
|
|
SUPPORTS_COLOR=false
|
|
SUPPORTS_STYLE=false
|
|
|
|
if [ -t 1 ] && command -v tput >/dev/null 2>&1; then
|
|
if [ "$(tput colors)" -ge 8 ]; then
|
|
SUPPORTS_COLOR=true
|
|
fi
|
|
if tput bold >/dev/null 2>&1 && tput smul >/dev/null 2>&1; then
|
|
SUPPORTS_STYLE=true
|
|
fi
|
|
fi
|
|
|
|
if [ "$SUPPORTS_COLOR" = true ]; then
|
|
cprint green "[OK] Terminal supports colored output."
|
|
else
|
|
cprint yellow "Note: Terminal does not support colored output. Continuing without formatting."
|
|
fi
|
|
|
|
if [ "$SUPPORTS_STYLE" = true ]; then
|
|
cprint green "[OK] Terminal supports bold and underline formatting."
|
|
else
|
|
cprint yellow "Note: Terminal does not support advanced text styles."
|
|
fi
|
|
}
|
|
|
|
# Check whether daemon or web is installed
|
|
is_component_installed() {
|
|
local component_name="$1"
|
|
local component_path="${install_dir}/${component_name}"
|
|
|
|
if [[ -d "$component_path" ]]; then
|
|
cprint green "Component '$component_name' is already installed at $component_path"
|
|
|
|
# Set corresponding global variable
|
|
if [[ "$component_name" == "daemon" ]]; then
|
|
daemon_installed=true
|
|
elif [[ "$component_name" == "web" ]]; then
|
|
web_installed=true
|
|
fi
|
|
|
|
return 0
|
|
else
|
|
cprint yellow "Component '$component_name' is not installed"
|
|
|
|
# Set corresponding global variable
|
|
if [[ "$component_name" == "daemon" ]]; then
|
|
daemon_installed=false
|
|
elif [[ "$component_name" == "web" ]]; then
|
|
web_installed=false
|
|
fi
|
|
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
check_component_permission() {
|
|
local component="$1"
|
|
local service_file="${systemd_file}${component}.service"
|
|
|
|
if [[ ! -f "$service_file" ]]; then
|
|
cprint yellow "Service file not found: $service_file"
|
|
return 0 # nothing changed
|
|
fi
|
|
|
|
# Extract the User= line if it exists
|
|
local user_line
|
|
user_line=$(grep -E '^User=' "$service_file" 2>/dev/null | head -1)
|
|
|
|
local user
|
|
if [[ -z "$user_line" ]]; then
|
|
user="root" # default if no User= is defined
|
|
else
|
|
user="${user_line#User=}"
|
|
fi
|
|
|
|
# Validate user
|
|
if [[ "$user" != "root" && "$user" != "mcsm" ]]; then
|
|
cprint red bold "Unsupported user '$user' in $service_file. Expected 'root' or 'mcsm'."
|
|
exit 1
|
|
fi
|
|
|
|
# Assign to appropriate global
|
|
if [[ "$component" == "web" ]]; then
|
|
web_installed_user="$user"
|
|
elif [[ "$component" == "daemon" ]]; then
|
|
daemon_installed_user="$user"
|
|
fi
|
|
|
|
cprint cyan "Detected $component installed as user: $user"
|
|
return 0
|
|
}
|
|
|
|
|
|
|
|
parse_args() {
|
|
local explicit_install_flag=false
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--install-dir)
|
|
if [[ -n "$2" ]]; then
|
|
install_dir="$2"
|
|
shift 2
|
|
else
|
|
echo "Error: --install-dir requires a path argument."
|
|
exit 1
|
|
fi
|
|
;;
|
|
--node-install-dir)
|
|
if [[ -n "$2" ]]; then
|
|
node_install_dir="$2"
|
|
shift 2
|
|
else
|
|
echo "Error: --node-install-dir requires a path argument."
|
|
exit 1
|
|
fi
|
|
;;
|
|
--install)
|
|
explicit_install_flag=true
|
|
if [[ -n "$2" && "$2" != --* ]]; then
|
|
case "$2" in
|
|
daemon)
|
|
install_daemon=true
|
|
is_component_installed "daemon"
|
|
install_web=false
|
|
check_component_permission "daemon"
|
|
;;
|
|
web)
|
|
install_daemon=false
|
|
is_component_installed "web"
|
|
install_web=true
|
|
check_component_permission "web"
|
|
;;
|
|
all)
|
|
install_daemon=true
|
|
install_web=true
|
|
is_component_installed "daemon"
|
|
is_component_installed "web"
|
|
check_component_permission "daemon"
|
|
check_component_permission "web"
|
|
;;
|
|
*)
|
|
echo "Error: Invalid value for --install. Expected 'daemon', 'web', or 'all'."
|
|
echo "Usage: --install daemon|web|all"
|
|
exit 1
|
|
;;
|
|
esac
|
|
shift 2
|
|
else
|
|
echo "Error: --install flag provided but no value. Please specify: daemon, web, or all."
|
|
echo "Usage: --install daemon|web|all"
|
|
exit 1
|
|
fi
|
|
;;
|
|
--user)
|
|
if [[ -n "$2" ]]; then
|
|
case "$2" in
|
|
root)
|
|
install_user="root"
|
|
;;
|
|
mcsm)
|
|
install_user="mcsm"
|
|
;;
|
|
*)
|
|
echo "Error: Invalid user '$2'. Only 'root' and 'mcsm' are supported."
|
|
echo "Usage: --user root|mcsm"
|
|
exit 1
|
|
;;
|
|
esac
|
|
shift 2
|
|
else
|
|
echo "Error: --user requires a value (root or mcsm)."
|
|
exit 1
|
|
fi
|
|
;;
|
|
--install-source)
|
|
if [[ -n "$2" ]]; then
|
|
install_source_path="$2"
|
|
shift 2
|
|
else
|
|
echo "Error: --install-source requires a file path."
|
|
exit 1
|
|
fi
|
|
;;
|
|
--force-permission)
|
|
force_permission=true
|
|
shift
|
|
;;
|
|
*)
|
|
echo "Error: Unknown argument: $1"
|
|
exit 1
|
|
;;
|
|
esac
|
|
done
|
|
|
|
# Auto-detect branch: only run if --install was not explicitly passed
|
|
if [[ "$explicit_install_flag" == false ]]; then
|
|
daemon_installed=false
|
|
web_installed=false
|
|
|
|
if is_component_installed "daemon"; then
|
|
daemon_installed=true
|
|
check_component_permission "daemon"
|
|
fi
|
|
if is_component_installed "web"; then
|
|
web_installed=true
|
|
check_component_permission "web"
|
|
fi
|
|
|
|
# When only one component installed, we wanted to process that one only.
|
|
if [[ "$daemon_installed" == true && "$web_installed" == false ]]; then
|
|
install_daemon=true
|
|
install_web=false
|
|
elif [[ "$daemon_installed" == false && "$web_installed" == true ]]; then
|
|
install_daemon=false
|
|
install_web=true
|
|
else
|
|
install_daemon=true
|
|
install_web=true
|
|
fi
|
|
fi
|
|
}
|
|
|
|
|
|
# Get Distribution & Architecture Info
|
|
detect_os_info() {
|
|
distro="Unknown"
|
|
version="Unknown"
|
|
arch=$(uname -m)
|
|
|
|
# Try primary source
|
|
if [ -f /etc/os-release ]; then
|
|
. /etc/os-release
|
|
distro_id="${ID,,}"
|
|
version_id="${VERSION_ID,,}"
|
|
|
|
case "$distro_id" in
|
|
ubuntu)
|
|
distro="Ubuntu"
|
|
version="$version_id"
|
|
;;
|
|
debian)
|
|
distro="Debian"
|
|
version="$version_id"
|
|
;;
|
|
centos)
|
|
distro="CentOS"
|
|
version="$version_id"
|
|
;;
|
|
rhel*)
|
|
distro="RHEL"
|
|
version="$version_id"
|
|
;;
|
|
arch)
|
|
distro="Arch"
|
|
version="rolling"
|
|
;;
|
|
*)
|
|
distro="${ID:-Unknown}"
|
|
version="$version_id"
|
|
;;
|
|
esac
|
|
fi
|
|
|
|
# Fallbacks for missing or invalid version
|
|
if [[ -z "$version" || "$version" == "unknown" || "$version" == "" ]]; then
|
|
if [ -f /etc/issue ]; then
|
|
version_guess=$(grep -oP '[0-9]+(\.[0-9]+)*' /etc/issue | head -1)
|
|
if [[ -n "$version_guess" ]]; then
|
|
version="$version_guess"
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
# Normalize version: if purely numeric/dotted keep only major; otherwise leave as-is
|
|
version_full="$version"
|
|
#if [[ "$version" =~ ^[0-9]+(\.[0-9]+)*$ ]]; then
|
|
# version="${version%%.*}"
|
|
#fi
|
|
cprint cyan "Detected OS: $distro $version_full"
|
|
cprint cyan "Detected Architecture: $arch"
|
|
}
|
|
|
|
# Check if all required commands are available
|
|
check_required_commands() {
|
|
local missing=0
|
|
|
|
for cmd in "${required_commands[@]}"; do
|
|
if ! command -v "$cmd" >/dev/null 2>&1; then
|
|
echo "Error: Required command '$cmd' is not available in PATH."
|
|
missing=1
|
|
fi
|
|
done
|
|
|
|
if [ "$missing" -ne 0 ]; then
|
|
echo "One or more required commands are missing. Please install them and try again."
|
|
return 1
|
|
fi
|
|
|
|
cprint green "All required commands are available."
|
|
return 0
|
|
}
|
|
|
|
# Print with specified color and style, fallback to RESET if not supported.
|
|
# Supported colors*: black|red|green|yellow|blue|magenta|cyan|white
|
|
# Supported styles*: bold|underline|italic|clear_line|strikethrough
|
|
# *Note: some style may not necessarily work on all terminals.
|
|
# Example usage:
|
|
# cprint green bold "Installation completed successfully."
|
|
# cprint red underline "Failed to detect required command: wget"
|
|
# cprint yellow "Warning: Disk space is low."
|
|
# cprint underline "Failed to detect required command: wget"
|
|
# cprint bold green underline"Installation completed successfully."
|
|
|
|
cprint() {
|
|
local color=""
|
|
local text=""
|
|
local styles=""
|
|
local disable_prefix=false
|
|
local disable_newline=false
|
|
|
|
while [[ $# -gt 1 ]]; do
|
|
case "$1" in
|
|
black|red|green|yellow|blue|magenta|cyan|white)
|
|
color="$1"
|
|
;;
|
|
bold|underline|italic|clear_line|strikethrough)
|
|
styles+="${STYLES[$1]}"
|
|
;;
|
|
noprefix)
|
|
disable_prefix=true
|
|
;;
|
|
nonl)
|
|
disable_newline=true
|
|
;;
|
|
esac
|
|
shift
|
|
done
|
|
|
|
text="$1"
|
|
|
|
local prefix_text=""
|
|
if [[ "$disable_prefix" != true ]]; then
|
|
local timestamp="[$(date +%H:%M:%S)]"
|
|
local label="[MCSM Installer]"
|
|
prefix_text="${FG_COLORS[white]}$timestamp $label${RESET} "
|
|
fi
|
|
|
|
local prefix=""
|
|
if [[ -n "$color" && "$SUPPORTS_COLOR" = true ]]; then
|
|
prefix+="${FG_COLORS[$color]}"
|
|
fi
|
|
if [[ "$SUPPORTS_STYLE" = true || "$styles" == *"${STYLES[clear_line]}"* ]]; then
|
|
prefix="$styles$prefix"
|
|
fi
|
|
|
|
if [[ "$disable_newline" == true ]]; then
|
|
printf "%b%b%s%b" "$prefix_text" "$prefix" "$text" "$RESET"
|
|
else
|
|
printf "%b%b%s%b\n" "$prefix_text" "$prefix" "$text" "$RESET"
|
|
fi
|
|
}
|
|
|
|
|
|
|
|
|
|
# Permission check before proceed with installation
|
|
permission_barrier() {
|
|
if [[ "$web_installed" == false && "$daemon_installed" == false ]]; then
|
|
cprint cyan "No components currently installed — skipping permission check."
|
|
return 0
|
|
fi
|
|
|
|
for component in web daemon; do
|
|
local is_installed_var="${component}_installed"
|
|
local installed_user_var="${component}_installed_user"
|
|
|
|
if [[ "${!is_installed_var}" == true ]]; then
|
|
local installed_user="${!installed_user_var}"
|
|
|
|
# Step 0: Ensure installed user is detected
|
|
if [[ -z "$installed_user" ]]; then
|
|
cprint red bold "Detected that '$component' is installed but could not determine the user from its systemd service file."
|
|
cprint red "This may indicate a custom or unsupported service file setup."
|
|
cprint red "Refusing to proceed to avoid potential conflicts."
|
|
exit 1
|
|
fi
|
|
|
|
# Step 1: User match check with optional force override
|
|
if [[ "$installed_user" != "$install_user" ]]; then
|
|
if [[ "$force_permission" == true ]]; then
|
|
cprint yellow bold "Permission mismatch for '$component':"
|
|
cprint yellow "Installed as user: $installed_user"
|
|
cprint yellow "Target install user: $install_user"
|
|
cprint yellow "User mismatch, but --force-permission is set. Continuing and updating permissions..."
|
|
sleep 3
|
|
else
|
|
cprint red bold "Permission mismatch for '$component':"
|
|
cprint red "Installed as user: $installed_user"
|
|
cprint red "Current install target user: $install_user"
|
|
cprint red "Unable to proceed due to ownership conflict. Use --force-permission to override and update file permission."
|
|
exit 1
|
|
fi
|
|
else
|
|
cprint green bold "Permission check passed: '$installed_user' matches target user."
|
|
fi
|
|
|
|
fi
|
|
done
|
|
|
|
# Step 2: Directory ownership check
|
|
local dir_owner
|
|
dir_owner=$(stat -c '%U' "$install_dir" 2>/dev/null)
|
|
|
|
if [[ -z "$dir_owner" ]]; then
|
|
cprint red bold "Unable to determine owner of install_dir: $install_dir"
|
|
exit 1
|
|
fi
|
|
|
|
if [[ "$dir_owner" != "$install_user" ]]; then
|
|
if [[ "$force_permission" == true ]]; then
|
|
cprint yellow bold "Install directory ownership mismatch:"
|
|
cprint yellow " Directory: $install_dir"
|
|
cprint yellow " Owned by: $dir_owner"
|
|
cprint yellow " Expected: $install_user"
|
|
cprint yellow " --force-permission is set. Continuing despite mismatch."
|
|
sleep 3
|
|
else
|
|
cprint red bold "Install directory ownership mismatch:"
|
|
cprint red " Directory: $install_dir"
|
|
cprint red " Owned by: $dir_owner"
|
|
cprint red " Expected: $install_user"
|
|
exit 1
|
|
fi
|
|
else
|
|
cprint green bold "Install directory ownership check passed: '$install_dir' is owned by '$install_user'."
|
|
fi
|
|
|
|
cprint green bold "Permissions and ownership validated. Proceeding."
|
|
return 0
|
|
}
|
|
|
|
|
|
|
|
# Map OS arch with actual Node.js Arch name
|
|
# This function should be placed after var arch has been assigned a valid value.
|
|
resolve_node_arch() {
|
|
case "$arch" in
|
|
x86_64)
|
|
node_arch="x64"
|
|
;;
|
|
aarch64)
|
|
node_arch="arm64"
|
|
;;
|
|
armv7l)
|
|
node_arch="armv7l"
|
|
;;
|
|
*)
|
|
cprint red bold "Unsupported architecture for Node.js: $arch"
|
|
return 1
|
|
;;
|
|
esac
|
|
|
|
# Assign node_path based on resolved arch and current version/install dir
|
|
node_path="${node_install_dir}/node-${node_version}-linux-${node_arch}"
|
|
|
|
cprint cyan "Resolved Node.js architecture: $node_arch"
|
|
cprint cyan "Node.js install path: $node_path"
|
|
}
|
|
|
|
# Check if Node.js at PATH is valid.
|
|
# This function check Node.js version + NPM (if Node.js valid)
|
|
verify_node_at_path() {
|
|
local node_path="$1"
|
|
node_bin_path="$node_path/bin/node"
|
|
npm_bin_path="$node_path/bin/npm"
|
|
|
|
# Node binary missing
|
|
if [ ! -x "$node_bin_path" ]; then
|
|
return 1
|
|
fi
|
|
|
|
local installed_ver
|
|
installed_ver="$("$node_bin_path" -v 2>/dev/null | sed 's/^v//')"
|
|
|
|
if [[ -z "$installed_ver" ]]; then
|
|
return 1
|
|
fi
|
|
|
|
if [ "$strict_node_version_check" = true ]; then
|
|
if [[ "$installed_ver" != "$required_node_ver" ]]; then
|
|
return 3
|
|
fi
|
|
else
|
|
local cmp
|
|
cmp=$(printf "%s\n%s\n" "$required_node_ver" "$installed_ver" | sort -V | head -1)
|
|
if [[ "$cmp" != "$required_node_ver" ]]; then
|
|
return 2
|
|
fi
|
|
fi
|
|
|
|
# Check if npm exists and works using node (not $PATH/npm)
|
|
if [ ! -x "$npm_bin_path" ]; then
|
|
return 4
|
|
fi
|
|
|
|
# Use node to run npm.js directly, in case env is broken
|
|
local npm_version
|
|
npm_version="$("$node_bin_path" "$npm_bin_path" --version 2>/dev/null)"
|
|
if [[ -z "$npm_version" ]]; then
|
|
return 4
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
|
|
# Node.js pre-check. check if we need to install Node.js before installer run.
|
|
# Use postcheck_node_after_install() to check after install.
|
|
check_node_installed() {
|
|
verify_node_at_path "$node_path"
|
|
local result=$?
|
|
|
|
case $result in
|
|
0)
|
|
cprint green bold "Node.js and npm found at $node_path (version $required_node_ver or compatible)"
|
|
install_node=false
|
|
;;
|
|
1)
|
|
cprint yellow bold "Node.js binary not found or unusable at $node_path"
|
|
install_node=true
|
|
;;
|
|
2)
|
|
cprint red bold "Node.js version at $node_path is too old. Required: >= $required_node_ver"
|
|
install_node=true
|
|
;;
|
|
3)
|
|
cprint red bold "Node.js version mismatch. Required: $required_node_ver, found something else."
|
|
install_node=true
|
|
;;
|
|
4)
|
|
cprint red bold "Node.js is present but npm is missing or broken."
|
|
install_node=true
|
|
;;
|
|
*)
|
|
cprint red bold "Unexpected error in node verification."
|
|
install_node=true
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# Node.js post-check. check if Node.js is valid after install.
|
|
postcheck_node_after_install() {
|
|
verify_node_at_path "$node_path"
|
|
if [[ $? -ne 0 ]]; then
|
|
cprint red bold "Node.js installation failed or is invalid at $node_path"
|
|
return 1
|
|
else
|
|
cprint green bold "Node.js is installed and functioning at $node_path"
|
|
return 0
|
|
fi
|
|
}
|
|
|
|
# Install Node.js and check
|
|
install_node() {
|
|
local archive_name="node-${node_version}-linux-${node_arch}.tar.xz"
|
|
local target_dir="${node_install_dir}/node-${node_version}-linux-${node_arch}"
|
|
local archive_path="${node_install_dir}/${archive_name}"
|
|
local download_url="${node_download_url_base}${node_version}/${archive_name}"
|
|
local fallback="$node_download_fallback"
|
|
|
|
cprint cyan bold "Installing Node.js $node_version for arch: $node_arch"
|
|
|
|
mkdir -p "$node_install_dir" || {
|
|
cprint red bold "Failed to create node install directory: $node_install_dir"
|
|
return 1
|
|
}
|
|
|
|
# Download
|
|
cprint cyan "Downloading Node.js from: $download_url"
|
|
if ! wget --progress=bar:force -O "$archive_path" "$download_url"; then
|
|
cprint yellow "Primary download failed. Attempting fallback..."
|
|
|
|
if [[ -n "$fallback" ]]; then
|
|
if [[ "$fallback" =~ ^https?:// ]]; then
|
|
cprint cyan "Downloading from fallback URL: $fallback"
|
|
if ! wget --progress=bar:force -O "$archive_path" "$fallback"; then
|
|
cprint red bold "Fallback download failed from: $fallback"
|
|
return 1
|
|
fi
|
|
elif [ -f "$fallback" ]; then
|
|
cprint cyan "Copying from local fallback: $fallback"
|
|
cp "$fallback" "$archive_path" || {
|
|
cprint red bold "Failed to copy fallback Node.js archive from $fallback"
|
|
return 1
|
|
}
|
|
else
|
|
cprint red bold "Invalid fallback path: $fallback"
|
|
return 1
|
|
fi
|
|
else
|
|
cprint red bold "No fallback source configured. Cannot proceed."
|
|
return 1
|
|
fi
|
|
fi
|
|
|
|
# Extract archive
|
|
cprint cyan "Extracting Node.js archive..."
|
|
if ! tar -xf "$archive_path" -C "$node_install_dir"; then
|
|
cprint red bold "Failed to extract Node.js archive."
|
|
return 1
|
|
fi
|
|
|
|
chmod -R a+rx "$target_dir" || {
|
|
cprint red bold "Failed to set execute permissions on Node.js files."
|
|
return 1
|
|
}
|
|
|
|
verify_node_at_path "$target_dir"
|
|
local result=$?
|
|
if [[ $result -ne 0 ]]; then
|
|
cprint red bold "Node.js installation failed verification."
|
|
return 1
|
|
fi
|
|
|
|
cprint cyan "Cleaning up archive..."
|
|
rm -f "$archive_path"
|
|
|
|
cprint green bold "Node.js $node_version installed successfully at $target_dir"
|
|
# Save resolved binary paths to global variables
|
|
node_bin_path="${target_dir}/bin/node"
|
|
npm_bin_path="${target_dir}/bin/npm"
|
|
|
|
cprint green "Node.js binary: $node_bin_path"
|
|
cprint green "npm binary: $npm_bin_path"
|
|
return 0
|
|
}
|
|
|
|
# Function to download MCSM package. fetch from primary URL first, then fallback URL.
|
|
# This function only put extracted file(s) into install_dir, it does not perform the actual update.
|
|
download_mcsm() {
|
|
local archive_name="$package_name"
|
|
local archive_path="${tmp_dir}/${archive_name}"
|
|
local primary_url="${download_base_url}${archive_name}"
|
|
local fallback="$download_fallback_url"
|
|
|
|
cprint cyan bold "Downloading MCSManager package..."
|
|
|
|
# Step 1: Try downloading from primary URL
|
|
if ! wget --progress=bar:force -O "$archive_path" "$primary_url"; then
|
|
cprint yellow "Primary download failed. Attempting fallback source..."
|
|
|
|
if [[ -z "$fallback" ]]; then
|
|
cprint red bold "No fallback URL or path specified."
|
|
return 1
|
|
fi
|
|
|
|
if [[ "$fallback" =~ ^https?:// ]]; then
|
|
if ! wget --progress=bar:force -O "$archive_path" "$fallback"; then
|
|
cprint red bold "Fallback download failed from $fallback"
|
|
return 1
|
|
fi
|
|
elif [[ -f "$fallback" ]]; then
|
|
cp "$fallback" "$archive_path" || {
|
|
cprint red bold "Failed to copy fallback archive from $fallback"
|
|
return 1
|
|
}
|
|
else
|
|
cprint red bold "Fallback path is invalid: $fallback"
|
|
return 1
|
|
fi
|
|
fi
|
|
|
|
# Step 2: Generate extract directory
|
|
local suffix
|
|
suffix=$(tr -dc 'a-z0-9' </dev/urandom | head -c 4)
|
|
local extracted_tmp_path="${tmp_dir}/mcsm_${suffix}"
|
|
|
|
if [[ -e "$extracted_tmp_path" ]]; then
|
|
cprint red bold "Temporary extract path already exists: $extracted_tmp_path"
|
|
return 1
|
|
fi
|
|
|
|
mkdir -p "$extracted_tmp_path" || {
|
|
cprint red bold "Failed to create temporary extract directory: $extracted_tmp_path"
|
|
return 1
|
|
}
|
|
|
|
cprint cyan "Extracting archive to $extracted_tmp_path..."
|
|
if ! tar -xzf "$archive_path" -C "$extracted_tmp_path"; then
|
|
cprint red bold "Failed to extract archive."
|
|
rm -rf "$extracted_tmp_path"
|
|
return 1
|
|
fi
|
|
|
|
rm -f "$archive_path"
|
|
|
|
# Step 3: Move the entire extracted directory to install_dir
|
|
install_tmp_dir="${install_dir}/mcsm_${suffix}"
|
|
|
|
if [[ -e "$install_tmp_dir" ]]; then
|
|
cprint red bold "Install target already exists at $install_tmp_dir"
|
|
cprint red " Please remove or rename it before proceeding."
|
|
return 1
|
|
fi
|
|
|
|
mv "$extracted_tmp_path" "$install_tmp_dir" || {
|
|
cprint red bold "Failed to move extracted files to $install_tmp_dir"
|
|
return 1
|
|
}
|
|
|
|
cprint green bold "MCSManager source extracted and moved to: $install_tmp_dir"
|
|
return 0
|
|
}
|
|
|
|
# Prepare user if needed
|
|
prepare_user() {
|
|
if [[ "$install_user" == "root" ]]; then
|
|
cprint cyan "install_user is 'root' — skipping user creation."
|
|
return 0
|
|
fi
|
|
|
|
# Check if user already exists
|
|
if id "$install_user" &>/dev/null; then
|
|
cprint green "User '$install_user' already exists."
|
|
else
|
|
cprint cyan "Creating system user: $install_user (nologin, no password)..."
|
|
if ! useradd --system --home "$install_dir" --shell /usr/sbin/nologin "$install_user"; then
|
|
cprint red bold "Failed to create user: $install_user"
|
|
exit 1
|
|
fi
|
|
cprint green "User '$install_user' created."
|
|
fi
|
|
|
|
|
|
# Docker integration
|
|
if command -v docker &>/dev/null; then
|
|
cprint cyan "Docker is installed — checking group assignment..."
|
|
|
|
if getent group docker &>/dev/null; then
|
|
if id -nG "$install_user" | grep -qw docker; then
|
|
cprint green "User '$install_user' is already in the 'docker' group."
|
|
else
|
|
cprint cyan "Adding user '$install_user' to 'docker' group..."
|
|
if usermod -aG docker "$install_user"; then
|
|
cprint green "Docker group access granted to '$install_user'."
|
|
else
|
|
cprint red "Failed to add '$install_user' to 'docker' group. Docker may not be usable by this user."
|
|
fi
|
|
fi
|
|
else
|
|
cprint red "Docker installed but 'docker' group not found. Skipping group assignment."
|
|
fi
|
|
else
|
|
cprint yellow "Docker not installed — skipping Docker group configuration."
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
# Function to stop MCSM services if they exist
|
|
stop_mcsm_services() {
|
|
cprint yellow bold "Attempting to stop mcsm-web and mcsm-daemon services..."
|
|
|
|
# Attempt to stop mcsm-web
|
|
cprint blue "Stopping mcsm-web..."
|
|
if systemctl stop mcsm-web; then
|
|
cprint green "mcsm-web stopped successfully."
|
|
else
|
|
cprint red bold "Warning: Failed to stop mcsm-web (may not exist or already stopped)."
|
|
fi
|
|
|
|
# Attempt to stop mcsm-daemon
|
|
cprint blue "Stopping mcsm-daemon..."
|
|
if systemctl stop mcsm-daemon; then
|
|
cprint green "mcsm-daemon stopped successfully."
|
|
else
|
|
cprint red bold "Warning: Failed to stop mcsm-daemon (may not exist or already stopped)."
|
|
fi
|
|
}
|
|
# Prepare file & permissions before install.
|
|
mcsm_install_prepare() {
|
|
|
|
# Stop service if existed
|
|
stop_mcsm_services
|
|
|
|
if [[ ! -d "$install_tmp_dir" ]]; then
|
|
cprint red bold "install_tmp_dir does not exist: $install_tmp_dir"
|
|
exit 1
|
|
fi
|
|
|
|
cprint cyan "Changing ownership of $install_tmp_dir to user '$install_user'..."
|
|
chown -R "$install_user":"$install_user" "$install_tmp_dir" || {
|
|
cprint red bold "Failed to change ownership of $install_tmp_dir"
|
|
cleanup_install_tmp
|
|
exit 1
|
|
}
|
|
|
|
# Normalize install_dir to ensure it ends with a slash
|
|
[[ "${install_dir}" != */ ]] && install_dir="${install_dir}/"
|
|
|
|
if [[ "$web_installed" == false && "$daemon_installed" == false ]]; then
|
|
cprint cyan "No existing components detected — skipping data backup/cleanup."
|
|
return 0
|
|
fi
|
|
|
|
cprint green bold "Existing components prepared successfully."
|
|
return 0
|
|
}
|
|
|
|
# Install or update a component
|
|
install_component() {
|
|
local component="$1"
|
|
local target_path="${install_dir}${component}"
|
|
local backup_data_path="${install_dir}${backup_prefix}${component}"
|
|
local source_path="${install_tmp_dir}/mcsmanager/${component}"
|
|
|
|
cprint cyan bold "Installing/Updating component: $component"
|
|
|
|
# Step 1: Move new component to install_dir
|
|
if [[ ! -d "$source_path" ]]; then
|
|
cprint red bold "Source directory not found: $source_path"
|
|
cleanup_install_tmp
|
|
exit 1
|
|
fi
|
|
|
|
if cp -a "$source_path"/. "$target_path"; then
|
|
cprint green "Updated files from $source_path → $target_path"
|
|
rm -rf "$source_path"
|
|
else
|
|
cprint red bold "Failed to update files from $source_path → $target_path"
|
|
cleanup_install_tmp
|
|
exit 1
|
|
fi
|
|
cprint green "Moved $component to $target_path"
|
|
|
|
|
|
# Step 3: Install NPM dependencies
|
|
if [[ ! -x "$npm_bin_path" ]]; then
|
|
cprint red bold "npm binary not found or not executable: $npm_bin_path"
|
|
cleanup_install_tmp
|
|
exit 1
|
|
fi
|
|
|
|
cprint cyan "Installing dependencies for $component using npm..."
|
|
pushd "$target_path" >/dev/null || {
|
|
cprint red bold "Failed to change directory to $target_path"
|
|
cleanup_install_tmp
|
|
exit 1
|
|
}
|
|
|
|
if ! "$node_bin_path" "$npm_bin_path" install --no-audit --no-fund --loglevel=warn; then
|
|
cprint red bold "NPM dependency installation failed for $component"
|
|
popd >/dev/null
|
|
cleanup_install_tmp
|
|
exit 1
|
|
fi
|
|
|
|
popd >/dev/null
|
|
cprint green bold "Component '$component' installed/updated successfully."
|
|
}
|
|
|
|
# Create systemd service for a given component.
|
|
# This will overwrite the existing service file.
|
|
create_systemd_service() {
|
|
local component="$1"
|
|
local service_path="${systemd_file}${component}.service"
|
|
local working_dir="${install_dir}${component}"
|
|
local exec="${node_bin_path} app.js"
|
|
|
|
if [[ ! -d "$working_dir" ]]; then
|
|
cprint red bold "Component directory not found: $working_dir"
|
|
cleanup_install_tmp
|
|
return 1
|
|
fi
|
|
|
|
cprint cyan "Creating systemd service for '$component'..."
|
|
|
|
cat > "$service_path" <<EOF
|
|
[Unit]
|
|
Description=MCSManager-${component^}
|
|
After=network.target
|
|
|
|
[Service]
|
|
Type=simple
|
|
WorkingDirectory=${working_dir}
|
|
ExecStart=${exec}
|
|
ExecReload=/bin/kill -s HUP \$MAINPID
|
|
ExecStop=/bin/kill -s TERM \$MAINPID
|
|
Restart=on-failure
|
|
User=${install_user}
|
|
Environment="PATH=${PATH}"
|
|
Environment="NODE_ENV=production"
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
EOF
|
|
|
|
if [[ $? -ne 0 ]]; then
|
|
cprint red bold "Failed to write service file: $service_path"
|
|
cleanup_install_tmp
|
|
return 1
|
|
fi
|
|
|
|
chmod 644 "$service_path"
|
|
cprint green "Created systemd unit: $service_path"
|
|
return 0
|
|
}
|
|
|
|
# Extract daemon key and/or http port
|
|
extract_component_info() {
|
|
# DAEMON SECTION
|
|
if [[ "$install_daemon" == true ]]; then
|
|
local daemon_service="mcsm-daemon.service"
|
|
local daemon_path="${install_dir}/daemon"
|
|
local daemon_config_path="${daemon_path}/${daemon_key_config_subpath}"
|
|
|
|
cprint cyan bold "Starting daemon service..."
|
|
if systemctl restart "$daemon_service"; then
|
|
cprint green "Daemon service started."
|
|
|
|
sleep 3 # Allow service to init and write configs
|
|
|
|
if [[ -f "$daemon_config_path" ]]; then
|
|
daemon_key=$(grep -oP '"key"\s*:\s*"\K[^"]+' "$daemon_config_path")
|
|
daemon_port=$(grep -oP '"port"\s*:\s*\K[0-9]+' "$daemon_config_path")
|
|
|
|
if [[ -n "$daemon_key" ]]; then
|
|
cprint green "Extracted daemon key: $daemon_key"
|
|
else
|
|
cprint red "Failed to extract daemon key from: $daemon_config_path"
|
|
fi
|
|
|
|
if [[ -n "$daemon_port" ]]; then
|
|
cprint green "Extracted daemon port: $daemon_port"
|
|
else
|
|
cprint red "Failed to extract daemon port from: $daemon_config_path"
|
|
fi
|
|
else
|
|
cprint red "Daemon config file not found: $daemon_config_path"
|
|
fi
|
|
else
|
|
cprint red bold "Failed to start daemon service: $daemon_service"
|
|
fi
|
|
fi
|
|
|
|
# WEB SECTION
|
|
if [[ "$install_web" == true ]]; then
|
|
local web_service="mcsm-web.service"
|
|
local web_path="${install_dir}/web"
|
|
local web_config_path="${web_path}/${web_port_config_subpath}"
|
|
|
|
cprint cyan bold "Starting web service..."
|
|
if systemctl restart "$web_service"; then
|
|
cprint green "Web service started."
|
|
|
|
sleep 3 # Allow time to populate config
|
|
|
|
if [[ -f "$web_config_path" ]]; then
|
|
web_port=$(grep -oP '"httpPort"\s*:\s*\K[0-9]+' "$web_config_path")
|
|
if [[ -n "$web_port" ]]; then
|
|
cprint green "Extracted web port: $web_port"
|
|
else
|
|
cprint red "Failed to extract web port from: $web_config_path"
|
|
fi
|
|
else
|
|
cprint red "Web config file not found: $web_config_path"
|
|
fi
|
|
else
|
|
cprint red bold "Failed to start web service: $web_service"
|
|
fi
|
|
fi
|
|
}
|
|
|
|
cleanup_install_tmp() {
|
|
if [[ -n "$install_tmp_dir" && -d "$install_tmp_dir" ]]; then
|
|
if rm -rf "$install_tmp_dir"; then
|
|
cprint green "Cleaned up temporary install folder: $install_tmp_dir"
|
|
else
|
|
cprint red "Failed to remove temporary folder: $install_tmp_dir"
|
|
fi
|
|
fi
|
|
}
|
|
|
|
print_install_result() {
|
|
# Clear the screen
|
|
clear || true
|
|
|
|
# Print ASCII banner
|
|
cprint white noprefix "______ _______________________ ___"
|
|
cprint white noprefix "___ |/ /_ ____/_ ___/__ |/ /_____ _____________ _______ _____________"
|
|
cprint white noprefix "__ /|_/ /_ / _____ \__ /|_/ /_ __ \`/_ __ \ __ \`/_ __ \`/ _ \_ ___/"
|
|
cprint white noprefix "_ / / / / /___ ____/ /_ / / / / /_/ /_ / / / /_/ /_ /_/ // __/ /"
|
|
cprint white noprefix "/_/ /_/ \____/ /____/ /_/ /_/ \__,_/ /_/ /_/\__,_/ _\__, / \___//_/"
|
|
echo ""
|
|
# status summary
|
|
cprint yellow noprefix "Installed/Updated Component(s):"
|
|
if [[ "$install_daemon" == true && -n "$daemon_key" && -n "$daemon_port" ]]; then
|
|
cprint white noprefix "Daemon"
|
|
elif [[ "$install_daemon" == true ]]; then
|
|
cprint white noprefix nonl "Daemon "
|
|
cprint yellow noprefix "(partial, config not fully detected)"
|
|
fi
|
|
|
|
if [[ "$install_web" == true && -n "$web_port" ]]; then
|
|
cprint white noprefix "Web"
|
|
elif [[ "$install_web" == true ]]; then
|
|
cprint white noprefix nonl "Web "
|
|
cprint yellow noprefix "(partial, config not fully detected)"
|
|
fi
|
|
|
|
echo ""
|
|
|
|
# Local IP detection
|
|
local ip_address
|
|
ip_address=$(hostname -I 2>/dev/null | awk '{print $1}')
|
|
[[ -z "$ip_address" ]] && ip_address="YOUR-IP"
|
|
|
|
# Daemon info
|
|
if [[ "$install_daemon" == true ]]; then
|
|
local daemon_address="ws://$ip_address:${daemon_port:-Failed to Retrieve from Config file}"
|
|
local daemon_key_display="${daemon_key:-Failed to Retrieve from Config file}"
|
|
|
|
cprint yellow noprefix "Daemon Address:"
|
|
cprint white noprefix " $daemon_address"
|
|
cprint yellow noprefix "Daemon Key:"
|
|
cprint white noprefix " $daemon_key_display"
|
|
echo ""
|
|
fi
|
|
|
|
# Web info
|
|
if [[ "$install_web" == true ]]; then
|
|
local web_address="http://$ip_address:${web_port:-Failed to Retrieve from Config file}"
|
|
cprint yellow noprefix "HTTP Web Interface:"
|
|
cprint white noprefix nonl " $web_address "
|
|
cprint yellow noprefix "(open in browser)"
|
|
echo ""
|
|
fi
|
|
|
|
# Port guidance
|
|
cprint yellow noprefix "NOTE:"
|
|
cprint white noprefix " Make sure to expose the above ports through your firewall."
|
|
cprint white noprefix " If accessing from outside your network, you may need to configure port forwarding on your router."
|
|
echo ""
|
|
|
|
# Service management help
|
|
cprint yellow noprefix "Service Management Commands:"
|
|
if [[ "$install_daemon" == true ]]; then
|
|
cprint white noprefix nonl " systemctl start "
|
|
cprint yellow noprefix "mcsm-daemon.service"
|
|
cprint white noprefix nonl " systemctl stop "
|
|
cprint yellow noprefix "mcsm-daemon.service"
|
|
cprint white noprefix nonl " systemctl restart "
|
|
cprint yellow noprefix "mcsm-daemon.service"
|
|
cprint white noprefix nonl " systemctl status "
|
|
cprint yellow noprefix "mcsm-daemon.service"
|
|
fi
|
|
if [[ "$install_web" == true ]]; then
|
|
cprint white noprefix nonl " systemctl start "
|
|
cprint yellow noprefix "mcsm-web.service"
|
|
cprint white noprefix nonl " systemctl stop "
|
|
cprint yellow noprefix "mcsm-web.service"
|
|
cprint white noprefix nonl " systemctl restart "
|
|
cprint yellow noprefix "mcsm-web.service"
|
|
cprint white noprefix nonl " systemctl status "
|
|
cprint yellow noprefix "mcsm-web.service"
|
|
fi
|
|
echo ""
|
|
|
|
# Official doc
|
|
cprint yellow noprefix "Official Documentation:"
|
|
cprint white noprefix " https://docs.mcsmanager.com/"
|
|
echo ""
|
|
|
|
# HTTPS support
|
|
cprint yellow noprefix "Need HTTPS?"
|
|
cprint white noprefix " To enable secure HTTPS access, configure a reverse proxy:"
|
|
cprint white noprefix " https://docs.mcsmanager.com/ops/proxy_https.html"
|
|
echo ""
|
|
|
|
if [[ "$force_permission" == true ]]; then
|
|
cprint red noprefix "[Important] You chose to override permission during install."
|
|
cprint red noprefix " You may need to run: chown -R $install_user <path> to update permission manually."
|
|
fi
|
|
|
|
# Closing message
|
|
cprint green noprefix "Installation completed. Enjoy managing your servers with MCSManager!"
|
|
echo ""
|
|
}
|
|
|
|
install_mcsm() {
|
|
local components=()
|
|
|
|
if [[ "$install_web" == true ]]; then
|
|
install_component "web"
|
|
create_systemd_service "web"
|
|
components+=("web")
|
|
fi
|
|
|
|
if [[ "$install_daemon" == true ]]; then
|
|
install_component "daemon"
|
|
create_systemd_service "daemon"
|
|
components+=("daemon")
|
|
fi
|
|
|
|
# Reload systemd after any service file changes
|
|
if (( ${#components[@]} > 0 )); then
|
|
cprint cyan "Reloading systemd daemon..."
|
|
# systemctl daemon-reexec
|
|
systemctl daemon-reload
|
|
|
|
for comp in "${components[@]}"; do
|
|
local svc="mcsm-${comp}.service"
|
|
|
|
cprint cyan "Enabling service: $svc"
|
|
if systemctl enable "$svc" &>/dev/null; then
|
|
cprint green "Enabled service: $svc"
|
|
else
|
|
cprint red bold "Failed to enable service: $svc"
|
|
cleanup_install_tmp
|
|
exit 1
|
|
fi
|
|
done
|
|
fi
|
|
|
|
# Clean tmp dir
|
|
cleanup_install_tmp
|
|
# Extract installed component info
|
|
safe_run extract_component_info "Failed to extract runtime info from installed services"
|
|
safe_run print_install_result "Failed to print installation result"
|
|
|
|
}
|
|
|
|
main() {
|
|
trap 'echo "Unexpected error occurred."; exit 99' ERR
|
|
safe_run detect_terminal_capabilities "Failed to detect terminal capabilities"
|
|
safe_run check_root "Script must be run as root"
|
|
safe_run parse_args "Failed to parse arguments" "$@"
|
|
safe_run detect_os_info "Failed to detect OS"
|
|
# To be moved to a master pre check function.
|
|
safe_run resolve_node_arch "Failed to resolve Node.js architecture"
|
|
|
|
safe_run check_required_commands "Missing required system commands"
|
|
|
|
safe_run check_node_installed "Failed to detect Node.js or npm at expected path. Node.js will be installed."
|
|
if [ "$install_node" = true ]; then
|
|
safe_run install_node "Node.js installation failed"
|
|
fi
|
|
|
|
safe_run permission_barrier "Permission validation failed — aborting install"
|
|
|
|
safe_run prepare_user "Failed to prepare user permission."
|
|
|
|
safe_run download_mcsm "Failed to acquire MCSManager source"
|
|
safe_run mcsm_install_prepare "Error while preparing for installation"
|
|
|
|
safe_run install_mcsm "Failed to install MCSManager"
|
|
}
|
|
main "$@" |