263 lines
7.7 KiB
Bash
263 lines
7.7 KiB
Bash
#!/bin/bash
|
|
|
|
set -euo pipefail
|
|
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
BLUE='\033[0;34m'
|
|
NC='\033[0m'
|
|
|
|
VERSION=${VERSION:-"v0.1.0"}
|
|
DOWNLOAD_URL="https://git.littlediary.cn/CN-JS-HuiBai/PromdataPanel/archive/${VERSION}.zip"
|
|
MIN_NODE_VERSION=18
|
|
SERVICE_NAME="promdatapanel"
|
|
SERVICE_FILE="/etc/systemd/system/${SERVICE_NAME}.service"
|
|
|
|
OS_ID=""
|
|
OS_VER=""
|
|
PROJECT_DIR=""
|
|
REAL_USER=""
|
|
|
|
echo -e "${BLUE}================================================${NC}"
|
|
echo -e "${BLUE} PromdataPanel Auto-Installer ${NC}"
|
|
echo -e "${BLUE} Version: ${VERSION} ${NC}"
|
|
echo -e "${BLUE}================================================${NC}"
|
|
|
|
detect_os() {
|
|
if [ -f /etc/os-release ]; then
|
|
# shellcheck disable=SC1091
|
|
. /etc/os-release
|
|
OS_ID="${ID:-}"
|
|
OS_VER="${VERSION_ID:-}"
|
|
else
|
|
echo -e "${RED}Error: Cannot detect operating system type (/etc/os-release missing).${NC}"
|
|
exit 1
|
|
fi
|
|
|
|
if [ -z "$OS_ID" ]; then
|
|
echo -e "${RED}Error: Unable to determine operating system ID.${NC}"
|
|
exit 1
|
|
fi
|
|
|
|
echo -e "Detected OS: ${GREEN}${OS_ID} ${OS_VER}${NC}"
|
|
}
|
|
|
|
require_cmd() {
|
|
local cmd="$1"
|
|
local hint="${2:-}"
|
|
if ! command -v "$cmd" >/dev/null 2>&1; then
|
|
echo -e "${RED}Missing required command: ${cmd}.${NC}"
|
|
if [ -n "$hint" ]; then
|
|
echo -e "${YELLOW}${hint}${NC}"
|
|
fi
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
install_packages() {
|
|
case "$OS_ID" in
|
|
ubuntu|debian|raspbian)
|
|
sudo apt-get update
|
|
sudo apt-get install -y "$@"
|
|
;;
|
|
centos|rhel|almalinux|rocky)
|
|
sudo yum install -y "$@"
|
|
;;
|
|
fedora)
|
|
sudo dnf install -y "$@"
|
|
;;
|
|
*)
|
|
echo -e "${RED}Unsupported OS for automatic package installation: ${OS_ID}${NC}"
|
|
echo -e "${YELLOW}Please install the following packages manually: $*${NC}"
|
|
exit 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
ensure_tooling() {
|
|
if ! command -v curl >/dev/null 2>&1; then
|
|
echo -e "${BLUE}Installing curl...${NC}"
|
|
install_packages curl
|
|
fi
|
|
|
|
if ! command -v unzip >/dev/null 2>&1; then
|
|
echo -e "${BLUE}Installing unzip...${NC}"
|
|
install_packages unzip
|
|
fi
|
|
}
|
|
|
|
configure_nodesource_apt_repo() {
|
|
sudo install -d -m 0755 /etc/apt/keyrings
|
|
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
|
|
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_20.x nodistro main" | sudo tee /etc/apt/sources.list.d/nodesource.list >/dev/null
|
|
}
|
|
|
|
install_node() {
|
|
echo -e "${BLUE}Verifying Node.js environment...${NC}"
|
|
|
|
local node_installed=false
|
|
if command -v node >/dev/null 2>&1; then
|
|
local current_node_ver
|
|
current_node_ver=$(node -v | cut -d'v' -f2 | cut -d'.' -f1)
|
|
if [ "$current_node_ver" -ge "$MIN_NODE_VERSION" ]; then
|
|
echo -e "${GREEN}Node.js $(node -v) is already installed.${NC}"
|
|
node_installed=true
|
|
else
|
|
echo -e "${YELLOW}Existing Node.js $(node -v) is too old (requires >= ${MIN_NODE_VERSION}).${NC}"
|
|
fi
|
|
fi
|
|
|
|
if [ "$node_installed" = true ]; then
|
|
return
|
|
fi
|
|
|
|
echo -e "${BLUE}Installing Node.js 20.x...${NC}"
|
|
case "$OS_ID" in
|
|
ubuntu|debian|raspbian)
|
|
install_packages ca-certificates curl gnupg
|
|
configure_nodesource_apt_repo
|
|
sudo apt-get update
|
|
sudo apt-get install -y nodejs
|
|
;;
|
|
centos|rhel|almalinux|rocky)
|
|
install_packages nodejs
|
|
;;
|
|
fedora)
|
|
install_packages nodejs
|
|
;;
|
|
*)
|
|
echo -e "${RED}Unsupported OS for automatic Node.js installation: ${OS_ID}${NC}"
|
|
echo -e "${YELLOW}Please install Node.js >= ${MIN_NODE_VERSION} manually.${NC}"
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
require_cmd node "Please install Node.js >= ${MIN_NODE_VERSION} manually and rerun the installer."
|
|
local installed_major
|
|
installed_major=$(node -v | cut -d'v' -f2 | cut -d'.' -f1)
|
|
if [ "$installed_major" -lt "$MIN_NODE_VERSION" ]; then
|
|
echo -e "${RED}Installed Node.js $(node -v) is still below the required version.${NC}"
|
|
echo -e "${YELLOW}Please upgrade Node.js manually to >= ${MIN_NODE_VERSION}.${NC}"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
download_project_if_needed() {
|
|
if [ -f "server/index.js" ]; then
|
|
return
|
|
fi
|
|
|
|
echo -e "${YELLOW}Project files not found. Starting download...${NC}"
|
|
ensure_tooling
|
|
|
|
local temp_dir
|
|
temp_dir=$(mktemp -d "${TMPDIR:-/tmp}/promdatapanel-install-XXXXXX")
|
|
local temp_zip="${temp_dir}/promdatapanel_${VERSION}.zip"
|
|
|
|
echo -e "${BLUE}Downloading ${DOWNLOAD_URL}...${NC}"
|
|
curl -fL "$DOWNLOAD_URL" -o "$temp_zip"
|
|
|
|
echo -e "${BLUE}Extracting files...${NC}"
|
|
unzip -q "$temp_zip" -d "$temp_dir"
|
|
|
|
local extracted_dir
|
|
extracted_dir=$(find "$temp_dir" -mindepth 1 -maxdepth 1 -type d | head -n 1)
|
|
if [ -z "$extracted_dir" ] || [ ! -f "$extracted_dir/server/index.js" ]; then
|
|
echo -e "${RED}Download succeeded, but archive structure is invalid.${NC}"
|
|
exit 1
|
|
fi
|
|
|
|
cd "$extracted_dir"
|
|
}
|
|
|
|
detect_runtime_user() {
|
|
if [ "$EUID" -eq 0 ]; then
|
|
REAL_USER="${SUDO_USER:-${USER:-root}}"
|
|
else
|
|
REAL_USER="${USER}"
|
|
fi
|
|
}
|
|
|
|
write_service_file() {
|
|
local node_path
|
|
node_path=$(command -v node)
|
|
if [ -z "$node_path" ]; then
|
|
echo -e "${RED}Unable to locate node executable after installation.${NC}"
|
|
exit 1
|
|
fi
|
|
|
|
local tmp_service
|
|
tmp_service=$(mktemp "${TMPDIR:-/tmp}/${SERVICE_NAME}.service.XXXXXX")
|
|
|
|
cat > "$tmp_service" <<EOF
|
|
[Unit]
|
|
Description=PromdataPanel Monitoring Dashboard
|
|
After=network.target mysql.service redis-server.service valkey-server.service
|
|
Wants=mysql.service
|
|
|
|
[Service]
|
|
Type=simple
|
|
User=${REAL_USER}
|
|
WorkingDirectory=${PROJECT_DIR}
|
|
ExecStart=${node_path} server/index.js
|
|
Restart=always
|
|
RestartSec=10
|
|
StandardOutput=journal
|
|
StandardError=journal
|
|
SyslogIdentifier=${SERVICE_NAME}
|
|
EnvironmentFile=-${PROJECT_DIR}/.env
|
|
Environment=NODE_ENV=production
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
EOF
|
|
|
|
echo -e "${BLUE}Creating systemd service at ${SERVICE_FILE}...${NC}"
|
|
sudo install -m 0644 "$tmp_service" "$SERVICE_FILE"
|
|
rm -f "$tmp_service"
|
|
}
|
|
|
|
detect_os
|
|
download_project_if_needed
|
|
detect_runtime_user
|
|
install_node
|
|
|
|
PROJECT_DIR=$(pwd)
|
|
echo -e "Project Directory: ${GREEN}${PROJECT_DIR}${NC}"
|
|
echo -e "Running User: ${GREEN}${REAL_USER}${NC}"
|
|
|
|
if [ ! -f ".env" ] && [ -f ".env.example" ]; then
|
|
echo -e "${BLUE}Creating .env from .env.example...${NC}"
|
|
cp .env.example .env
|
|
fi
|
|
|
|
echo -e "${BLUE}Installing NPM dependencies...${NC}"
|
|
npm install --production
|
|
|
|
write_service_file
|
|
|
|
echo -e "${BLUE}Reloading systemd and restarting service...${NC}"
|
|
sudo systemctl daemon-reload
|
|
sudo systemctl enable "$SERVICE_NAME"
|
|
sudo systemctl restart "$SERVICE_NAME"
|
|
|
|
echo -e "${BLUE}Checking service status...${NC}"
|
|
sleep 2
|
|
if sudo systemctl is-active --quiet "$SERVICE_NAME"; then
|
|
echo -e "${GREEN}SUCCESS: PromdataPanel is now running.${NC}"
|
|
PORT=$(grep "^PORT=" .env 2>/dev/null | cut -d'=' -f2 || true)
|
|
PORT=${PORT:-3000}
|
|
IP_ADDR=$(hostname -I 2>/dev/null | awk '{print $1}')
|
|
if [ -n "${IP_ADDR:-}" ]; then
|
|
echo -e "Dashboard URL: ${YELLOW}http://${IP_ADDR}:${PORT}${NC}"
|
|
fi
|
|
else
|
|
echo -e "${RED}FAILED: Service failed to start.${NC}"
|
|
echo -e "Check logs with: ${BLUE}journalctl -u ${SERVICE_NAME} -xe${NC}"
|
|
fi
|
|
|
|
echo -e "${BLUE}================================================${NC}"
|
|
echo -e "${GREEN}Installation completed!${NC}"
|
|
echo -e "${BLUE}================================================${NC}"
|