diff --git a/install.sh b/install.sh index 6990659..097021a 100644 --- a/install.sh +++ b/install.sh @@ -3,16 +3,16 @@ # ============================================================================ # IDC设备管理系统 - Linux 安装引导脚本 # ============================================================================ -# +# # 功能:检测并安装 Node.js,然后运行 install.js -# +# # 使用方法: # 方式1 - 直接运行: # chmod +x install.sh && ./install.sh -# +# # 方式2 - 一键安装(推荐): # curl -fsSL https://your-domain/install.sh | bash -# +# # 方式3 - 从 GitHub: # curl -fsSL https://raw.githubusercontent.com/xxx/xxx/main/install.sh | bash # @@ -20,41 +20,51 @@ set -e -# 颜色定义 RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[0;33m' CYAN='\033[0;36m' BRIGHT='\033[1m' +DIM='\033[2m' +MAGENTA='\033[0;35m' +WHITE='\033[0;37m' RESET='\033[0m' -# 日志函数 -log_info() { echo -e "${CYAN}ℹ${RESET} $1"; } -log_success() { echo -e "${GREEN}✓${RESET} $1"; } -log_warning() { echo -e "${YELLOW}⚠${RESET} $1"; } -log_error() { echo -e "${RED}✗${RESET} $1"; } -log_step() { echo -e "\n${BRIGHT}${CYAN}▶ $1${RESET}"; } -log_divider() { echo -e "${YELLOW}$(printf '─%.0s' {1..60})${RESET}"; } +PIPE="│" +ARROW="▶" +POINTER="❯" +DIAMOND="◆" +CHECK="✔" +CROSS="✖" +WARN="⚠" +INFO="ℹ" + +log_info() { echo -e " ${CYAN}${INFO}${RESET} $1"; } +log_success() { echo -e " ${GREEN}${CHECK}${RESET} $1"; } +log_warning() { echo -e " ${YELLOW}${WARN}${RESET} $1"; } +log_error() { echo -e " ${RED}${CROSS}${RESET} $1"; } +log_step() { echo -e "\n ${BRIGHT}${CYAN}${ARROW}${RESET} ${BRIGHT}$1${RESET}"; } +log_divider() { echo -e " ${DIM}$(printf '─%.0s' {1..56})${RESET}"; } +log_thick_divider() { echo -e " ${DIM}$(printf '═%.0s' {1..56})${RESET}"; } -# 脚本版本 SCRIPT_VERSION="1.0.0" MIN_NODE_VERSION=16 -# 获取脚本所在目录 SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -# 显示欢迎信息 show_banner() { - echo -e " -${BRIGHT}${CYAN} -╔══════════════════════════════════════════════════════════╗ -║ IDC设备管理系统 - Linux 安装引导脚本 v${SCRIPT_VERSION} ║ -║ Linux Bootstrap Installation Script ║ -╚══════════════════════════════════════════════════════════╝ -${RESET}" + local width=58 + local line=$(printf '═%.0s' $(seq 1 $width)) + + echo -e "" + echo -e " ${CYAN}╔${line}╗${RESET}" + echo -e " ${CYAN}║${BRIGHT}${WHITE} IDC 设备管理系统 - Linux 安装引导脚本${RESET} ${CYAN}║${RESET}" + echo -e " ${CYAN}║${DIM} Linux Bootstrap Installation Script${RESET} ${CYAN}║${RESET}" + echo -e " ${CYAN}║$(printf '%*s' $width '')║${RESET}" | sed "s/ / v${SCRIPT_VERSION} /2" + echo -e " ${CYAN}╚${line}╝${RESET}" + echo -e "" } -# 检测 Linux 发行版 detect_distro() { if [ -f /etc/os-release ]; then . /etc/os-release @@ -66,12 +76,10 @@ detect_distro() { fi } -# 检查是否为 root 用户 is_root() { [ "$(id -u)" -eq 0 ] } -# 获取 sudo 前缀 get_sudo() { if is_root; then echo "" @@ -80,12 +88,10 @@ get_sudo() { fi } -# 检查命令是否存在 command_exists() { command -v "$1" >/dev/null 2>&1 } -# 获取 Node.js 版本号 get_node_version() { if command_exists node; then node -e "console.log(process.versions.node.split('.')[0])" @@ -94,33 +100,27 @@ get_node_version() { fi } -# 检查 Node.js 版本 check_node_version() { local version=$(get_node_version) [ "$version" -ge "$MIN_NODE_VERSION" ] } -# 安装 Node.js - Ubuntu/Debian install_node_debian() { local sudo=$(get_sudo) log_info "使用 NodeSource 安装 Node.js 20.x..." - - # 安装 curl(如果没有) + if ! command_exists curl; then log_info "安装 curl..." $sudo apt-get update -qq $sudo apt-get install -y -qq curl fi - - # 添加 NodeSource 源 + log_info "添加 NodeSource 源..." curl -fsSL https://deb.nodesource.com/setup_20.x | $sudo -E bash - - - # 安装 Node.js + log_info "安装 Node.js..." $sudo apt-get install -y -qq nodejs - - # 验证安装 + if command_exists node; then log_success "Node.js $(node -v) 安装成功" return 0 @@ -129,26 +129,21 @@ install_node_debian() { fi } -# 安装 Node.js - CentOS/RHEL/Fedora install_node_rhel() { local sudo=$(get_sudo) log_info "使用 NodeSource 安装 Node.js 20.x..." - - # 安装 curl(如果没有) + if ! command_exists curl; then log_info "安装 curl..." $sudo yum install -y -q curl fi - - # 添加 NodeSource 源 + log_info "添加 NodeSource 源..." curl -fsSL https://rpm.nodesource.com/setup_20.x | $sudo bash - - - # 安装 Node.js + log_info "安装 Node.js..." $sudo yum install -y -q nodejs - - # 验证安装 + if command_exists node; then log_success "Node.js $(node -v) 安装成功" return 0 @@ -157,13 +152,12 @@ install_node_rhel() { fi } -# 安装 Node.js - Arch Linux install_node_arch() { local sudo=$(get_sudo) log_info "使用 pacman 安装 Node.js..." - + $sudo pacman -S --noconfirm nodejs npm - + if command_exists node; then log_success "Node.js $(node -v) 安装成功" return 0 @@ -172,26 +166,22 @@ install_node_arch() { fi } -# 安装 Node.js - 使用 nvm install_node_nvm() { log_info "使用 nvm 安装 Node.js..." - - # 安装 nvm + if [ ! -d "$HOME/.nvm" ]; then log_info "安装 nvm..." curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash fi - - # 加载 nvm + export NVM_DIR="$HOME/.nvm" [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" - - # 安装 Node.js 20 + log_info "安装 Node.js 20..." nvm install 20 nvm use 20 nvm alias default 20 - + if command_exists node; then log_success "Node.js $(node -v) 安装成功" return 0 @@ -200,13 +190,12 @@ install_node_nvm() { fi } -# 自动安装 Node.js auto_install_node() { local distro=$(detect_distro) - + log_step "自动安装 Node.js" log_info "检测到 Linux 发行版: $distro" - + case $distro in ubuntu|debian) install_node_debian @@ -224,62 +213,55 @@ auto_install_node() { esac } -# 显示手动安装指引 show_manual_install_guide() { - echo -e " -${BRIGHT}Node.js 手动安装指引:${RESET} + echo -e "" + echo -e " ${BRIGHT}${MAGENTA}${DIAMOND}${RESET} ${BRIGHT}Node.js 手动安装指引${RESET}" + log_divider() -${YELLOW}Ubuntu/Debian:${RESET} - curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - - sudo apt-get install -y nodejs - -${YELLOW}CentOS/RHEL/Fedora:${RESET} - curl -fsSL https://rpm.nodesource.com/setup_20.x | sudo bash - - sudo yum install -y nodejs - -${YELLOW}Arch Linux:${RESET} - sudo pacman -S nodejs npm - -${YELLOW}使用 nvm(推荐开发者):${RESET} - curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash - source ~/.bashrc - nvm install 20 - nvm use 20 - -${YELLOW}验证安装:${RESET} - node -v # 应显示 v20.x.x - npm -v # 应显示 10.x.x - -${CYAN}安装完成后,请重新运行此脚本继续安装 IDC设备管理系统${RESET} -" + echo -e " ${PIPE} ${YELLOW}Ubuntu/Debian:${RESET}" + echo -e " ${PIPE} ${CYAN}curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -${RESET}" + echo -e " ${PIPE} ${CYAN}sudo apt-get install -y nodejs${RESET}" + echo -e "" + echo -e " ${PIPE} ${YELLOW}CentOS/RHEL/Fedora:${RESET}" + echo -e " ${PIPE} ${CYAN}curl -fsSL https://rpm.nodesource.com/setup_20.x | sudo bash -${RESET}" + echo -e " ${PIPE} ${CYAN}sudo yum install -y nodejs${RESET}" + echo -e "" + echo -e " ${PIPE} ${YELLOW}Arch Linux:${RESET}" + echo -e " ${PIPE} ${CYAN}sudo pacman -S nodejs npm${RESET}" + echo -e "" + echo -e " ${PIPE} ${YELLOW}使用 nvm(推荐开发者):${RESET}" + echo -e " ${PIPE} ${CYAN}curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash${RESET}" + echo -e " ${PIPE} ${CYAN}source ~/.bashrc && nvm install 20 && nvm use 20${RESET}" + echo -e "" + echo -e " ${PIPE} ${YELLOW}验证安装:${RESET}" + echo -e " ${PIPE} ${CYAN}node -v${RESET} # 应显示 v20.x.x" + echo -e " ${PIPE} ${CYAN}npm -v${RESET} # 应显示 10.x.x" + log_divider() + log_info "安装完成后,请重新运行此脚本继续安装 IDC 设备管理系统" } -# 检查并安装依赖 check_and_install_dependencies() { log_step "检查系统依赖" - + local sudo=$(get_sudo) local distro=$(detect_distro) local needs_install=() - - # 检查 git + if ! command_exists git; then needs_install+=("git") fi - - # 检查 curl + if ! command_exists curl; then needs_install+=("curl") fi - - # 检查 wget(可选) + if ! command_exists wget; then needs_install+=("wget") fi - + if [ ${#needs_install[@]} -gt 0 ]; then log_info "需要安装依赖: ${needs_install[*]}" - + case $distro in ubuntu|debian) $sudo apt-get update -qq @@ -296,43 +278,39 @@ check_and_install_dependencies() { return 1 ;; esac - + log_success "依赖安装完成" else log_success "系统依赖已满足" fi } -# 主函数 main() { show_banner - - # 检查是否在正确的目录 + if [ ! -f "$SCRIPT_DIR/install.js" ]; then log_error "未找到 install.js 文件" log_info "请在项目根目录运行此脚本" exit 1 fi - - # 检查 Node.js + log_step "检查 Node.js 环境" - + if command_exists node; then local node_version=$(get_node_version) log_info "检测到 Node.js: $(node -v)" - + if check_node_version; then log_success "Node.js 版本满足要求 (>= v$MIN_NODE_VERSION)" else log_warning "Node.js 版本过低 (v$node_version < v$MIN_NODE_VERSION)" - - echo -e "\n${YELLOW}请选择操作:${RESET}" - echo " 1) 自动升级 Node.js 到 v20(推荐)" - echo " 2) 显示手动安装指引" - echo " 3) 退出" - - read -p "请选择 [1-3]: " choice - + + echo -e " ${POINTER} ${CYAN}1${RESET}) 自动升级 Node.js 到 v20(推荐)" + echo -e " ${DIM}2${RESET}) 显示手动安装指引" + echo -e " ${DIM}3${RESET}) 退出" + + read -p " ❯ 请选择 [1-3]: " choice + case $choice in 1) auto_install_node || { @@ -340,7 +318,6 @@ main() { show_manual_install_guide exit 1 } - # 重新加载环境 export PATH="/usr/bin:$HOME/.nvm/versions/node/v20*/bin:$PATH" hash -r ;; @@ -360,14 +337,13 @@ main() { fi else log_warning "未检测到 Node.js" - - echo -e "\n${YELLOW}请选择操作:${RESET}" - echo " 1) 自动安装 Node.js v20(推荐)" - echo " 2) 显示手动安装指引" - echo " 3) 退出" - - read -p "请选择 [1-3]: " choice - + + echo -e " ${POINTER} ${CYAN}1${RESET}) 自动安装 Node.js v20(推荐)" + echo -e " ${DIM}2${RESET}) 显示手动安装指引" + echo -e " ${DIM}3${RESET}) 退出" + + read -p " ❯ 请选择 [1-3]: " choice + case $choice in 1) check_and_install_dependencies @@ -376,7 +352,6 @@ main() { show_manual_install_guide exit 1 } - # 重新加载环境 export PATH="/usr/bin:$HOME/.nvm/versions/node/v20*/bin:$PATH" hash -r ;; @@ -394,17 +369,16 @@ main() { ;; esac fi - - # 检查 npm + log_step "检查 npm" if command_exists npm; then log_success "npm $(npm -v)" else log_warning "npm 未安装,尝试自动安装..." - + local distro=$(detect_distro) local sudo=$(get_sudo) - + case $distro in ubuntu|debian) $sudo apt-get update -qq @@ -417,23 +391,20 @@ main() { $sudo pacman -S --noconfirm npm ;; *) - # 尝试使用 Node.js 自带的 corepack 启用 npm if command_exists corepack; then log_info "尝试使用 corepack 启用 npm..." corepack enable corepack prepare npm@latest --activate else log_error "无法自动安装 npm,请手动安装" - echo -e "\n${YELLOW}手动安装 npm:${RESET}" - echo " Ubuntu/Debian: sudo apt-get install npm" - echo " CentOS/RHEL: sudo yum install npm" - echo " 或重新安装 Node.js: curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - && sudo apt-get install -y nodejs" + echo -e " ${PIPE} ${YELLOW}手动安装 npm:${RESET}" + echo -e " ${PIPE} Ubuntu/Debian: ${CYAN}sudo apt-get install npm${RESET}" + echo -e " ${PIPE} CentOS/RHEL: ${CYAN}sudo yum install npm${RESET}" exit 1 fi ;; esac - - # 再次检查 npm + if command_exists npm; then log_success "npm $(npm -v) 安装成功" else @@ -441,18 +412,16 @@ main() { exit 1 fi fi - + log_divider log_success "环境检查通过!" - - # 运行 install.js + log_step "启动安装程序" log_info "正在运行 install.js..." echo "" - + cd "$SCRIPT_DIR" node install.js "$@" } -# 运行主函数 main "$@" diff --git a/install/env-check.js b/install/env-check.js index f26c5dd..6ae9c5f 100644 --- a/install/env-check.js +++ b/install/env-check.js @@ -1,7 +1,7 @@ const fs = require('fs'); const { execSync } = require('child_process'); const { MIN_NODE_VERSION } = require('./constants'); -const { colors, log } = require('./logger'); +const { colors, ICONS, log } = require('./logger'); const { runCommand, commandExists } = require('./utils'); const { ask } = require('./ui'); @@ -75,7 +75,7 @@ function showNodeInstallGuide() { const isMac = platform === 'darwin'; const isLinux = platform === 'linux'; - console.log('\n' + colors.bright + 'Node.js 安装指引:' + colors.reset); + log.section('Node.js 安装指引'); if (isLinux) { const distro = detectLinuxDistro(); @@ -293,9 +293,8 @@ async function installNodeViaNvm() { async function handleNodeNotInstalledLinux() { log.error('未检测到 Node.js'); - console.log('\n' + colors.yellow + '您可以选择:' + colors.reset); - console.log(` ${colors.cyan}1.${colors.reset} 自动安装 Node.js 20.x(推荐,需要 sudo 权限)`); - console.log(` ${colors.cyan}2.${colors.reset} 显示手动安装指引,退出脚本`); + console.log(` ${ICONS.pointer} ${colors.cyan}1.${colors.reset} 自动安装 Node.js 20.x(推荐,需要 sudo 权限)`); + console.log(` ${colors.dim}2.${colors.reset} 显示手动安装指引,退出脚本`); const choice = await ask('请选择', '1'); @@ -362,9 +361,8 @@ async function checkEnvironment() { log.error(`Node.js 版本过低 (${version}),需要 >= 14.0.0`); if (isLinux) { - console.log('\n' + colors.yellow + '您可以选择:' + colors.reset); - console.log(` ${colors.cyan}1.${colors.reset} 自动升级到 Node.js 20.x`); - console.log(` ${colors.cyan}2.${colors.reset} 显示手动升级指引,退出脚本`); + console.log(` ${ICONS.pointer} ${colors.cyan}1.${colors.reset} 自动升级到 Node.js 20.x`); + console.log(` ${colors.dim}2.${colors.reset} 显示手动升级指引,退出脚本`); const upgradeChoice = await ask('请选择', '1'); if (upgradeChoice === '1') { diff --git a/install/generator.js b/install/generator.js index c4684cf..e5affbb 100644 --- a/install/generator.js +++ b/install/generator.js @@ -1,6 +1,6 @@ const fs = require('fs'); const path = require('path'); -const { colors, log } = require('./logger'); +const { colors, ICONS, log } = require('./logger'); const { generateSecretKey } = require('./utils'); const { config } = require('./config'); const { ask } = require('./ui'); @@ -213,8 +213,8 @@ server { if (isWindows) { log.info('Windows Nginx 配置路径示例:'); - console.log(` 将配置文件复制到: ${colors.cyan}C:/nginx/conf/conf.d/idc.conf${colors.reset}`); - console.log(` 或修改主配置 include: ${colors.cyan}C:/nginx/conf/nginx.conf${colors.reset}`); + console.log(` ${ICONS.pipe} 将配置文件复制到: ${colors.cyan}C:/nginx/conf/conf.d/idc.conf${colors.reset}`); + console.log(` ${ICONS.pipe} 或修改主配置 include: ${colors.cyan}C:/nginx/conf/nginx.conf${colors.reset}`); } } catch (error) { log.error(`Nginx 配置文件生成失败: ${error.message}`); @@ -225,22 +225,26 @@ server { async function confirmConfiguration() { log.step('配置确认'); - console.log('\n' + colors.bright + '部署配置摘要:' + colors.reset); - console.log(` 数据库类型: ${colors.cyan}${config.dbType}${colors.reset}`); + console.log(` ${colors.bright}部署配置摘要${colors.reset}`); + log.thickDivider(); + + log.keyValue('数据库类型', config.dbType); if (config.dbType === 'mysql') { - console.log(` MySQL主机: ${colors.cyan}${config.dbConfig.host}:${config.dbConfig.port}${colors.reset}`); - console.log(` 数据库名: ${colors.cyan}${config.dbConfig.database}${colors.reset}`); - console.log(` 用户名: ${colors.cyan}${config.dbConfig.username}${colors.reset}`); + log.keyValue('MySQL 主机', `${config.dbConfig.host}:${config.dbConfig.port}`); + log.keyValue('数据库名', config.dbConfig.database); + log.keyValue('用户名', config.dbConfig.username); } - console.log(` 后端端口: ${colors.cyan}${config.backendPort}${colors.reset}`); - console.log(` 运行环境: ${colors.cyan}${config.nodeEnv}${colors.reset}`); - console.log(` 前端部署: ${colors.cyan}${config.frontendDeploy}${colors.reset}`); - console.log(` 前端端口: ${colors.cyan}${config.frontendPort}${colors.reset}`); + log.keyValue('后端端口', String(config.backendPort)); + log.keyValue('运行环境', config.nodeEnv); + log.keyValue('前端部署', config.frontendDeploy); + log.keyValue('前端端口', String(config.frontendPort)); if (config.frontendDeploy === 'nginx') { - console.log(` 域名: ${colors.cyan}${config.domain}${colors.reset}`); + log.keyValue('域名', config.domain); } - const confirm = await ask('\n确认以上配置并开始部署? (Y/n)', 'Y'); + log.thickDivider(); + + const confirm = await ask('确认以上配置并开始部署? (Y/n)', 'Y'); if (confirm.toLowerCase() !== 'y') { log.info('已取消部署'); process.exit(0); diff --git a/install/index.js b/install/index.js index 1b0e9bd..dae9778 100644 --- a/install/index.js +++ b/install/index.js @@ -1,5 +1,5 @@ const { SCRIPT_VERSION, LOG_DIR } = require('./constants'); -const { colors, log, initLogFile, closeLogFile } = require('./logger'); +const { colors, ICONS, log, padCenter } = require('./logger'); const { ask, closeReadline } = require('./ui'); const { config, saveConfig, loadSavedConfig, applySavedConfig, parseArgs, showHelp } = require('./config'); const { checkEnvironment } = require('./env-check'); @@ -14,35 +14,45 @@ let installStartTime = Date.now(); function printSummary() { const duration = ((Date.now() - installStartTime) / 1000).toFixed(1); - - console.log(` -${colors.bright}${colors.magenta} -╔══════════════════════════════════════════════════════════╗ -║ 安装摘要 ║ -╚══════════════════════════════════════════════════════════╝${colors.reset}`); - console.log(`\n ${colors.cyan}安装耗时:${colors.reset} ${duration} 秒`); - console.log(` ${colors.cyan}数据库类型:${colors.reset} ${config.dbType}`); - console.log(` ${colors.cyan}后端端口:${colors.reset} ${config.backendPort}`); - console.log(` ${colors.cyan}前端部署:${colors.reset} ${config.frontendDeploy}`); - - console.log(`\n${colors.bright}服务管理命令:${colors.reset}`); - console.log(` ${colors.cyan}pm2 status${colors.reset} 查看服务状态`); - console.log(` ${colors.cyan}pm2 logs idc-backend${colors.reset} 查看后端日志`); - console.log(` ${colors.cyan}pm2 restart idc-backend${colors.reset} 重启后端`); - console.log(` ${colors.cyan}pm2 stop idc-backend${colors.reset} 停止后端`); - - console.log(`\n${colors.bright}访问地址:${colors.reset}`); - console.log(` 后端API: ${colors.cyan}http://localhost:${config.backendPort}/api${colors.reset}`); + log.banner( + '安装部署完成', + 'Installation Complete', + null + ); + + console.log(` ${ICONS.pipe} ${colors.dim}安装耗时${colors.reset} ${colors.cyan}${duration} 秒${colors.reset}`); + console.log(` ${ICONS.pipe} ${colors.dim}数据库类型${colors.reset} ${colors.cyan}${config.dbType}${colors.reset}`); + console.log(` ${ICONS.pipe} ${colors.dim}后端端口${colors.reset} ${colors.cyan}${config.backendPort}${colors.reset}`); + console.log(` ${ICONS.pipe} ${colors.dim}前端部署${colors.reset} ${colors.cyan}${config.frontendDeploy}${colors.reset}`); if (config.frontendDeploy === 'nginx') { - console.log(` 前端页面: ${colors.cyan}http://${config.domain}:${config.frontendPort}${colors.reset}`); - } else { - console.log(` 前端页面: ${colors.cyan}http://localhost:${config.frontendPort}${colors.reset}`); + console.log(` ${ICONS.pipe} ${colors.dim}前端端口${colors.reset} ${colors.cyan}${config.frontendPort}${colors.reset}`); + console.log(` ${ICONS.pipe} ${colors.dim}域名${colors.reset} ${colors.cyan}${config.domain}${colors.reset}`); } - - console.log(`\n${colors.bright}更新升级:${colors.reset}`); - console.log(` ${colors.cyan}node update.js${colors.reset} 一键更新`); - + + log.divider(); + + log.section('服务管理命令'); + console.log(` ${ICONS.pipe} ${colors.cyan}pm2 status${colors.reset} 查看服务状态`); + console.log(` ${ICONS.pipe} ${colors.cyan}pm2 logs idc-backend${colors.reset} 查看后端日志`); + console.log(` ${ICONS.pipe} ${colors.cyan}pm2 restart idc-backend${colors.reset} 重启后端`); + console.log(` ${ICONS.pipe} ${colors.cyan}pm2 stop idc-backend${colors.reset} 停止后端`); + + log.divider(); + + log.section('访问地址'); + console.log(` ${ICONS.pipe} ${colors.dim}后端 API${colors.reset} ${colors.cyan}http://localhost:${config.backendPort}/api${colors.reset}`); + if (config.frontendDeploy === 'nginx') { + console.log(` ${ICONS.pipe} ${colors.dim}前端页面${colors.reset} ${colors.cyan}http://${config.domain}:${config.frontendPort}${colors.reset}`); + } else { + console.log(` ${ICONS.pipe} ${colors.dim}前端页面${colors.reset} ${colors.cyan}http://localhost:${config.frontendPort}${colors.reset}`); + } + + log.divider(); + + log.section('更新升级'); + console.log(` ${ICONS.pipe} ${colors.cyan}node update.js${colors.reset} 一键更新`); + log.divider(); log.success('安装部署完成!'); } @@ -58,13 +68,11 @@ async function main() { initLogFile(LOG_DIR); - console.log(` -${colors.bright}${colors.cyan} -╔══════════════════════════════════════════════════════════╗ -║ IDC设备管理系统 - 安装部署脚本 v${SCRIPT_VERSION} ║ -║ Interactive Installation & Deployment Script ║ -╚══════════════════════════════════════════════════════════╝ -${colors.reset}`); + log.banner( + 'IDC 设备管理系统', + '安装部署脚本', + SCRIPT_VERSION + ); if (cmdArgs.nonInteractive) { log.info('运行模式: 非交互式(使用默认配置)'); @@ -94,26 +102,26 @@ ${colors.reset}`); if (useSavedConfig) { log.step('数据库配置'); - log.info(`数据库类型: ${config.dbType}`); + log.keyValue('数据库类型', config.dbType); if (config.dbType === 'mysql') { - log.info(`MySQL: ${config.dbConfig.host}:${config.dbConfig.port}/${config.dbConfig.database}`); + log.keyValue('MySQL 地址', `${config.dbConfig.host}:${config.dbConfig.port}/${config.dbConfig.database}`); } log.divider(); log.step('服务配置'); - log.info(`后端端口: ${config.backendPort}`); - log.info(`运行环境: ${config.nodeEnv}`); - log.info(`前端部署: ${config.frontendDeploy}`); - log.info(`前端端口: ${config.frontendPort}`); + log.keyValue('后端端口', String(config.backendPort)); + log.keyValue('运行环境', config.nodeEnv); + log.keyValue('前端部署', config.frontendDeploy); + log.keyValue('前端端口', String(config.frontendPort)); if (config.frontendDeploy === 'nginx') { - log.info(`域名: ${config.domain}`); + log.keyValue('域名', config.domain); } log.divider(); } else { await configureDatabase(cmdArgs); await configureServices(cmdArgs); } - + if (!cmdArgs.nonInteractive) { await confirmConfiguration(); } @@ -128,13 +136,13 @@ ${colors.reset}`); await installDependencies(); await initDatabase(); - + if (!cmdArgs.skipBuild) { await buildFrontend(); } else { log.info('已跳过前端构建'); } - + await startServices(); if (process.platform !== 'win32' && config.frontendDeploy === 'nginx') { diff --git a/install/logger.js b/install/logger.js index 10e4810..9c0e9e6 100644 --- a/install/logger.js +++ b/install/logger.js @@ -6,31 +6,119 @@ let logFileStream = null; const colors = { reset: '\x1b[0m', bright: '\x1b[1m', + dim: '\x1b[2m', green: '\x1b[32m', yellow: '\x1b[33m', red: '\x1b[31m', cyan: '\x1b[36m', gray: '\x1b[90m', - magenta: '\x1b[35m' + magenta: '\x1b[35m', + blue: '\x1b[34m', + bgCyan: '\x1b[46m', + bgBlue: '\x1b[44m', + white: '\x1b[37m', }; +const ICONS = { + info: 'ℹ', + success: '✔', + warning: '⚠', + error: '✖', + arrow: '▶', + bullet: '●', + pointer: '❯', + check: '✓', + cross: '✗', + dash: '─', + doubleDash: '═', + corner: '└', + pipe: '│', + diamond: '◆', + star: '★', +}; + +function padCenter(text, width) { + const stripped = text.replace(/\x1b\[[0-9;]*m/g, ''); + const len = stripped.length; + const pad = Math.max(0, width - len); + const left = Math.floor(pad / 2); + const right = pad - left; + return ' '.repeat(left) + text + ' '.repeat(right); +} + +function progressBar(current, total, width = 20) { + const filled = Math.round((current / total) * width); + const empty = width - filled; + const bar = `${colors.cyan}${'━'.repeat(filled)}${colors.dim}${'╌'.repeat(empty)}${colors.reset}`; + return bar; +} + const log = { - info: (msg) => console.log(`${colors.cyan}ℹ${colors.reset} ${msg}`), - success: (msg) => console.log(`${colors.green}✓${colors.reset} ${msg}`), - warning: (msg) => console.log(`${colors.yellow}⚠${colors.reset} ${msg}`), - error: (msg) => console.log(`${colors.red}✗${colors.reset} ${msg}`), + info: (msg) => console.log(` ${colors.cyan}${ICONS.info}${colors.reset} ${msg}`), + success: (msg) => console.log(` ${colors.green}${ICONS.success}${colors.reset} ${msg}`), + warning: (msg) => console.log(` ${colors.yellow}${ICONS.warning}${colors.reset} ${msg}`), + error: (msg) => console.log(` ${colors.red}${ICONS.error}${colors.reset} ${msg}`), + step: (msg) => { const idx = INSTALL_STEPS.indexOf(msg); if (idx >= 0) { currentStepIndex = idx; } - const progress = currentStepIndex < INSTALL_STEPS.length - ? `${colors.gray}[${currentStepIndex + 1}/${INSTALL_STEPS.length}]${colors.reset} ` - : ''; - console.log(`\n${progress}${colors.bright}${colors.cyan}▶ ${msg}${colors.reset}`); + const current = currentStepIndex + 1; + const total = INSTALL_STEPS.length; + const bar = progressBar(current, total, 15); + const progress = `${colors.dim}[${current}/${total}]${colors.reset} ${bar}`; + console.log(''); + console.log(`${progress}`); + console.log(` ${colors.bright}${colors.cyan}${ICONS.arrow}${colors.reset} ${colors.bright}${msg}${colors.reset}`); + }, + + divider: () => console.log(` ${colors.dim}${ICONS.dash.repeat(56)}${colors.reset}`), + thickDivider: () => console.log(` ${colors.dim}${ICONS.doubleDash.repeat(56)}${colors.reset}`), + + subStep: (msg) => console.log(` ${colors.dim}${ICONS.corner}${colors.reset} ${msg}`), + + keyValue: (key, value) => { + const keyStr = `${colors.dim}${key}${colors.reset}`; + const valueStr = `${colors.cyan}${value}${colors.reset}`; + console.log(` ${ICONS.pipe} ${keyStr} ${valueStr}`); + }, + + banner: (title, subtitle, version) => { + const width = 58; + const top = `${colors.bright}${colors.cyan}${ICONS.doubleDash.repeat(width + 2)}${colors.reset}`; + const bottom = top; + const titleLine = padCenter(`${colors.bright}${colors.white}${title}${colors.reset}`, width); + const subtitleLine = padCenter(`${colors.dim}${subtitle}${colors.reset}`, width); + const versionLine = version ? padCenter(`${colors.cyan}v${version}${colors.reset}`, width) : ''; + + console.log(''); + console.log(` ${colors.cyan}╔${top.substring(2)}╗${colors.reset}`); + console.log(` ${colors.cyan}║${titleLine}║${colors.reset}`); + if (subtitle) { + console.log(` ${colors.cyan}║${subtitleLine}║${colors.reset}`); + } + if (version) { + console.log(` ${colors.cyan}║${versionLine}║${colors.reset}`); + } + console.log(` ${colors.cyan}╚${bottom.substring(2)}╝${colors.reset}`); + console.log(''); + }, + + section: (title) => { + console.log(''); + console.log(` ${colors.bright}${colors.magenta}${ICONS.diamond}${colors.reset} ${colors.bright}${title}${colors.reset}`); + log.divider(); + }, + + tableRow: (items, widths) => { + const parts = items.map((item, i) => { + const stripped = item.replace(/\x1b\[[0-9;]*m/g, ''); + const pad = Math.max(0, (widths[i] || 20) - stripped.length); + return item + ' '.repeat(pad); + }); + console.log(` ${ICONS.pipe} ${parts.join(' ')}`); }, - divider: () => console.log(`${colors.gray}${'─'.repeat(60)}${colors.reset}`), - subStep: (msg) => console.log(` ${colors.gray}└${colors.reset} ${msg}`) }; function initLogFile(LOG_DIR) { @@ -62,7 +150,9 @@ function closeLogFile() { module.exports = { colors, + ICONS, log, + padCenter, initLogFile, closeLogFile, }; diff --git a/install/nginx.js b/install/nginx.js index 38ac7a4..f8e9d84 100644 --- a/install/nginx.js +++ b/install/nginx.js @@ -1,7 +1,7 @@ const fs = require('fs'); const path = require('path'); const { execSync } = require('child_process'); -const { colors, log } = require('./logger'); +const { colors, ICONS, log } = require('./logger'); const { ask, select } = require('./ui'); const { config } = require('./config'); const { runCommand } = require('./utils'); @@ -12,9 +12,9 @@ function showNginxInstallGuideLinux() { const root = isRootUser(); const sudoPrefix = root ? '' : 'sudo '; - console.log('\n' + colors.bright + 'Nginx 安装命令:' + colors.reset); + log.section('Nginx 安装命令'); if (root) { - console.log(colors.gray + '(当前以 root 用户运行,无需 sudo)' + colors.reset); + console.log(` ${ICONS.pipe} ${colors.gray}(当前以 root 用户运行,无需 sudo)${colors.reset}`); } switch (distro) { diff --git a/install/ui.js b/install/ui.js index 25edf08..0506b0a 100644 --- a/install/ui.js +++ b/install/ui.js @@ -1,5 +1,5 @@ const readline = require('readline'); -const { log } = require('./logger'); +const { colors, ICONS, log } = require('./logger'); const rl = readline.createInterface({ input: process.stdin, @@ -17,7 +17,8 @@ rl.on('error', (err) => { function ask(question, defaultValue = '') { return new Promise((resolve) => { - const prompt = defaultValue ? `${question} (${defaultValue}): ` : `${question}: `; + const defaultHint = defaultValue ? `${colors.dim}(${defaultValue})${colors.reset}` : ''; + const prompt = ` ${colors.cyan}${ICONS.pointer}${colors.reset} ${colors.bright}${question}${colors.reset} ${defaultHint}${colors.dim}:${colors.reset} `; rl.question(prompt, (answer) => { resolve(answer.trim() || defaultValue); }); @@ -25,10 +26,10 @@ function ask(question, defaultValue = '') { } function askPassword(question) { - return new Promise((resolve) => { - const prompt = `${question}: `; - process.stdout.write(prompt); + const prompt = ` ${colors.cyan}${ICONS.pointer}${colors.reset} ${colors.bright}${question}${colors.reset} ${colors.dim}:${colors.reset} `; + process.stdout.write(prompt); + return new Promise((resolve) => { let password = ''; const stdin = process.stdin; const stdout = process.stdout; @@ -52,7 +53,8 @@ function askPassword(question) { resolve(password); } else if (code === 3) { cleanup(); - stdout.write('\n已取消\n'); + stdout.write('\n'); + log.warning('已取消'); process.exit(0); } else if (code === 127 || code === 8) { if (password.length > 0) { @@ -78,10 +80,22 @@ function askPassword(question) { } async function select(question, options, defaultIndex = 1) { - const { colors } = require('./logger'); - console.log(`\n${question}`); + console.log(''); + console.log(` ${colors.bright}${question}${colors.reset}`); + log.divider(); + options.forEach((opt, idx) => { - console.log(` ${colors.cyan}${idx + 1}.${colors.reset} ${opt.label}`); + const isDefault = idx + 1 === defaultIndex; + const marker = isDefault + ? `${colors.cyan}${ICONS.pointer}${colors.reset}` + : ` `; + const label = isDefault + ? `${colors.bright}${opt.label}${colors.reset}` + : `${colors.dim}${opt.label}${colors.reset}`; + const num = isDefault + ? `${colors.cyan}${idx + 1}${colors.reset}` + : `${colors.dim}${idx + 1}${colors.reset}`; + console.log(` ${marker} ${num}. ${label}`); }); const answer = await ask('请选择', String(defaultIndex));