Files
yunrui_asset/install.js
T

2083 lines
70 KiB
JavaScript
Raw Normal View History

#!/usr/bin/env node
/**
* ============================================================================
* IDC设备管理系统 - 交互式安装部署脚本
* ============================================================================
*
* 功能说明:
* - 交互式配置数据库(SQLite/MySQL
* - 交互式选择前端部署方式(Nginx/PM2 serve
* - 自动检测并可选安装 NginxWindows
* - 自动安装项目依赖
* - 自动初始化数据库
* - 自动构建前端项目
* - 使用 PM2 启动和管理后端服务
*
* 使用方法:
* node install.js # 交互式安装
* node install.js --help # 查看帮助
* node install.js --non-interactive # 非交互式(使用默认配置)
*
* 系统要求:
* - Node.js >= 16.0.0
* - npm >= 6.0.0
* - Windows/Linux/macOS
*
* 作者:自动生成
* 日期:2026-02-05
* ============================================================================
*/
const readline = require('readline');
const { execSync, spawn } = require('child_process');
const fs = require('fs');
const path = require('path');
const SCRIPT_VERSION = '2.0.0';
const MIN_NODE_VERSION = 16;
const LOG_DIR = path.join(__dirname, 'logs');
const colors = {
reset: '\x1b[0m',
bright: '\x1b[1m',
green: '\x1b[32m',
yellow: '\x1b[33m',
red: '\x1b[31m',
cyan: '\x1b[36m',
gray: '\x1b[90m',
magenta: '\x1b[35m'
};
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}`),
step: (msg) => console.log(`\n${colors.bright}${colors.cyan}${msg}${colors.reset}`),
divider: () => console.log(`${colors.gray}${'─'.repeat(60)}${colors.reset}`),
subStep: (msg) => console.log(` ${colors.gray}${colors.reset} ${msg}`)
};
let logFileStream = null;
let installStartTime = null;
function parseArgs() {
const args = process.argv.slice(2);
return {
nonInteractive: args.includes('--non-interactive') || args.includes('-y'),
skipNginx: args.includes('--skip-nginx'),
skipBuild: args.includes('--skip-build'),
dbType: args.find(a => a.startsWith('--db='))?.split('=')[1],
backendPort: args.find(a => a.startsWith('--port='))?.split('=')[1],
help: args.includes('--help') || args.includes('-h')
};
}
function showHelp() {
console.log(`
${colors.bright}IDC设备管理系统 - 安装部署脚本 v${SCRIPT_VERSION}${colors.reset}
用法: node install.js [选项]
选项:
-y, --non-interactive 非交互式安装(使用默认配置)
--skip-nginx 跳过 Nginx 配置
--skip-build 跳过前端构建
--db=<type> 数据库类型 (sqlite/mysql)
--port=<port> 后端端口
-h, --help 显示帮助信息
示例:
node install.js # 交互式安装
node install.js -y # 使用默认配置快速安装
node install.js -y --db=mysql # 使用 MySQL 数据库
node install.js -y --port=3000 # 指定后端端口
`);
process.exit(0);
}
function initLogFile() {
if (!fs.existsSync(LOG_DIR)) {
fs.mkdirSync(LOG_DIR, { recursive: true });
}
const logFileName = `install_${new Date().toISOString().replace(/[:.]/g, '-')}.log`;
const logFilePath = path.join(LOG_DIR, logFileName);
logFileStream = fs.createWriteStream(logFilePath, { flags: 'a' });
const originalConsoleLog = console.log;
console.log = (...args) => {
const timestamp = new Date().toISOString();
const message = args.map(arg =>
typeof arg === 'string' ? arg.replace(/\x1b\[[0-9;]*m/g, '') : String(arg)
).join(' ');
logFileStream.write(`[${timestamp}] ${message}\n`);
originalConsoleLog.apply(console, args);
};
}
function closeLogFile() {
if (logFileStream) {
logFileStream.end();
}
}
// =============================================================================
// 配置存储对象
// =============================================================================
/**
* 部署配置对象
* 存储用户交互过程中设置的所有配置参数
*
* @property {string} dbType - 数据库类型:'sqlite' 或 'mysql'
* @property {Object} dbConfig - MySQL 配置参数(当 dbType='mysql' 时使用)
* @property {number} backendPort - 后端服务监听端口
* @property {string} nodeEnv - 运行环境:'development' 或 'production'
* @property {string} frontendDeploy - 前端部署方式:'nginx' 或 'pm2'
* @property {number} frontendPort - 前端服务监听端口
* @property {string} domain - 域名配置(Nginx 使用)
*/
const config = {
dbType: 'sqlite', // 默认使用 SQLite
dbConfig: {}, // MySQL 配置初始为空
backendPort: 8000, // 默认后端端口
nodeEnv: 'production', // 默认运行环境
frontendDeploy: 'nginx', // 默认使用 Nginx
frontendPort: 80, // 默认 Nginx 端口
domain: 'localhost' // 默认域名
};
// =============================================================================
// 交互式输入函数
// =============================================================================
/**
* 创建 readline 接口实例
* 用于在命令行中读取用户输入
*/
const rl = readline.createInterface({
input: process.stdin, // 标准输入(键盘)
output: process.stdout // 标准输出(屏幕)
});
rl.on('close', () => {
process.exit(0);
});
rl.on('error', (err) => {
log.error(`输入错误: ${err.message}`);
process.exit(1);
});
/**
* 提问函数 - 获取用户输入
*
* @param {string} question - 提示问题文本
* @param {string} defaultValue - 默认值(用户直接回车时使用)
* @returns {Promise<string>} 用户输入的内容
*
* 使用示例:
* const port = await ask('请输入端口', '8000');
*/
function ask(question, defaultValue = '') {
return new Promise((resolve) => {
const prompt = defaultValue ? `${question} (${defaultValue}): ` : `${question}: `;
rl.question(prompt, (answer) => {
resolve(answer.trim() || defaultValue);
});
});
}
2026-03-11 14:28:00 +08:00
/**
* 密码输入函数 - 隐藏输入内容并显示星号
2026-03-11 14:28:00 +08:00
*
* @param {string} question - 提示问题文本
* @param {boolean} requireConfirm - 是否需要确认密码
2026-03-11 14:28:00 +08:00
* @returns {Promise<string>} 用户输入的密码
*
* 使用示例:
* const password = await askPassword('请输入密码');
* const password = await askPassword('请输入密码', true); // 需要确认
2026-03-11 14:28:00 +08:00
*/
function askPassword(question, requireConfirm = false) {
2026-03-11 14:28:00 +08:00
return new Promise((resolve) => {
const rl2 = readline.createInterface({
input: process.stdin,
output: process.stdout
});
2026-03-11 14:28:00 +08:00
rl2.question(`${question}: `, { silent: true }, (password1) => {
if (requireConfirm) {
rl2.question('请再次输入密码确认: `, { silent: true }, (password2) => {
rl2.close();
if (password1 === password2) {
resolve(password1);
} else {
log.error('两次输入的密码不一致请重新输入');
resolve(askPassword(question, requireConfirm));
}
});
} else {
rl2.close();
resolve(password1);
2026-03-11 14:28:00 +08:00
}
});
2026-03-11 14:28:00 +08:00
});
}
/**
* 选择函数 - 显示选项列表并获取用户选择
*
* @param {string} question - 提示问题文本
* @param {Array<{label: string, value: any}>} options - 选项数组
* @returns {Promise<any>} 用户选择的值
*
* 使用示例:
* const dbType = await select('选择数据库', [
* { label: 'SQLite', value: 'sqlite' },
* { label: 'MySQL', value: 'mysql' }
* ]);
*/
async function select(question, options) {
console.log(`\n${question}`);
options.forEach((opt, idx) => {
console.log(` ${colors.cyan}${idx + 1}.${colors.reset} ${opt.label}`);
});
const answer = await ask('请选择', '1');
const index = parseInt(answer) - 1;
return options[index]?.value || options[0].value;
}
2026-03-11 14:28:00 +08:00
/**
* 生成随机密钥
*
* @param {number} length - 密钥长度,默认64位
* @returns {string} 随机密钥
*/
function generateSecretKey(length = 64) {
const crypto = require('crypto');
return crypto.randomBytes(length).toString('hex');
}
// =============================================================================
// 系统命令执行函数
// =============================================================================
/**
* 执行系统命令
*
* @param {string} command - 要执行的命令
* @param {Object} options - 执行选项
* @param {boolean} options.silent - 是否静默执行(不显示输出)
* @param {string} options.cwd - 工作目录
* @returns {Object} 执行结果 { success: boolean, output?: string, error?: string }
*
* 使用示例:
* const result = runCommand('npm install', { cwd: './backend' });
* if (result.success) console.log('安装成功');
*/
function runCommand(command, options = {}) {
try {
// 使用 execSync 同步执行命令
const result = execSync(command, {
encoding: 'utf8', // 输出编码为 UTF-8
stdio: options.silent ? 'pipe' : 'inherit', // 是否显示命令输出
cwd: options.cwd || process.cwd(), // 设置工作目录
shell: true // 使用系统 shell 执行
});
return { success: true, output: result };
} catch (error) {
// 命令执行失败时返回错误信息
return { success: false, error: error.message };
}
}
/**
* 检查命令是否存在于系统中
*
* @param {string} command - 命令名称
* @returns {boolean} 是否存在
*
* 使用示例:
* if (commandExists('pm2')) console.log('PM2 已安装');
*/
function commandExists(command) {
try {
// 尝试执行命令的 --version 参数
execSync(`${command} --version`, { stdio: 'pipe', shell: true });
return true;
} catch {
return false;
}
}
// =============================================================================
// 环境检测函数
// =============================================================================
/**
* 检查 Node.js 版本是否满足要求
*
* @returns {boolean} 版本是否 >= MIN_NODE_VERSION
*/
function checkNodeVersion() {
const version = process.version;
const major = parseInt(version.slice(1).split('.')[0]);
return major >= MIN_NODE_VERSION;
}
/**
* 检测 Nginx 是否已安装
*
* 检测逻辑:
* 1. 尝试执行 nginx -v 命令
* 2. Windows 系统额外检查常见安装路径
*
* @returns {boolean} 是否已安装
*/
function isNginxInstalled() {
try {
// 方法1:尝试执行 nginx 命令
execSync('nginx -v', { stdio: 'pipe', shell: true });
return true;
} catch {
// 方法2:Windows 下检查常见安装路径
if (process.platform === 'win32') {
const commonPaths = [
'C:\\nginx\\nginx.exe',
'C:\\Program Files\\nginx\\nginx.exe',
'C:\\Program Files (x86)\\nginx\\nginx.exe'
];
for (const p of commonPaths) {
if (fs.existsSync(p)) return true;
}
}
return false;
}
}
/**
* 检查 Node.js 和 npm 是否已安装
*
* @returns {Object} 检查结果 { nodeInstalled: boolean, npmInstalled: boolean, version?: string }
*/
function checkNodeAndNpm() {
let nodeInstalled = false;
let npmInstalled = false;
let version = '';
try {
// 检查 Node.js
version = process.version;
nodeInstalled = true;
} catch {
nodeInstalled = false;
}
// 检查 npm
npmInstalled = commandExists('npm');
return { nodeInstalled, npmInstalled, version };
}
/**
* 检测 Linux 发行版类型
*
* @returns {string} 发行版类型:'ubuntu' | 'debian' | 'centos' | 'rhel' | 'fedora' | 'arch' | 'unknown'
*/
function detectLinuxDistro() {
try {
// 尝试读取 /etc/os-release 文件
const osRelease = fs.readFileSync('/etc/os-release', 'utf8');
if (osRelease.includes('Ubuntu')) return 'ubuntu';
if (osRelease.includes('Debian')) return 'debian';
if (osRelease.includes('CentOS')) return 'centos';
if (osRelease.includes('Red Hat')) return 'rhel';
if (osRelease.includes('Fedora')) return 'fedora';
if (osRelease.includes('Arch')) return 'arch';
return 'unknown';
} catch {
return 'unknown';
}
}
/**
* 显示 Node.js 安装指引
*
* 以 Linux 为主,同时支持 Windows 和 macOS
* 根据操作系统显示对应的安装方式和下载链接
*/
function showNodeInstallGuide() {
const platform = process.platform;
const isWindows = platform === 'win32';
const isMac = platform === 'darwin';
const isLinux = platform === 'linux';
console.log('\n' + colors.bright + 'Node.js 安装指引' + colors.reset);
if (isLinux) {
// Linux 为主,优先显示
const distro = detectLinuxDistro();
console.log('\n' + colors.green + 'Linux 推荐安装方式' + colors.reset);
// 根据检测到的发行版显示对应命令
switch (distro) {
case 'ubuntu':
case 'debian':
console.log('\n' + colors.yellow + `Ubuntu/Debian (${distro}):` + colors.reset);
console.log(` ${colors.cyan}# 使用 NodeSource 源安装 Node.js 20.x` + colors.reset);
console.log(` ${colors.cyan}curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -${colors.reset}`);
console.log(` ${colors.cyan}sudo apt-get install -y nodejs${colors.reset}`);
break;
case 'centos':
case 'rhel':
case 'fedora':
console.log('\n' + colors.yellow + `CentOS/RHEL/Fedora (${distro}):` + colors.reset);
console.log(` ${colors.cyan}# 使用 NodeSource 源安装 Node.js 20.x` + colors.reset);
console.log(` ${colors.cyan}curl -fsSL https://rpm.nodesource.com/setup_20.x | sudo bash -${colors.reset}`);
console.log(` ${colors.cyan}sudo yum install -y nodejs${colors.reset}`);
break;
case 'arch':
console.log('\n' + colors.yellow + 'Arch Linux:' + colors.reset);
console.log(` ${colors.cyan}sudo pacman -S nodejs npm${colors.reset}`);
break;
default:
console.log('\n' + colors.yellow + '通用 Linux 安装方式' + colors.reset);
console.log(` ${colors.cyan}# Ubuntu/Debian${colors.reset}`);
console.log(` curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -`);
console.log(` sudo apt-get install -y nodejs`);
console.log(`\n ${colors.cyan}# CentOS/RHEL/Fedora${colors.reset}`);
console.log(` curl -fsSL https://rpm.nodesource.com/setup_20.x | sudo bash -`);
console.log(` sudo yum install -y nodejs`);
}
// 所有 Linux 都显示 nvm 方式
console.log('\n' + colors.yellow + '开发者推荐使用 nvmNode Version Manager:' + colors.reset);
console.log(` ${colors.cyan}# 1. 安装 nvm${colors.reset}`);
console.log(` curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash`);
console.log(` ${colors.cyan}# 2. 加载 nvm(或重新打开终端)${colors.reset}`);
console.log(` export NVM_DIR="$HOME/.nvm" && [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"`);
console.log(` ${colors.cyan}# 3. 安装并使用 Node.js 20${colors.reset}`);
console.log(` nvm install 20`);
console.log(` nvm use 20`);
console.log(` ${colors.cyan}# 4. 验证安装${colors.reset}`);
console.log(` node -v && npm -v`);
// Docker 方式
console.log('\n' + colors.yellow + 'Docker 方式' + colors.reset);
console.log(` ${colors.cyan}# 使用官方 Node 镜像${colors.reset}`);
console.log(` docker run -it --rm node:20-alpine node -v`);
} else if (isWindows) {
// Windows
console.log('\n' + colors.green + 'Windows 安装方式' + colors.reset);
console.log('\n' + colors.yellow + '方式一使用官方安装包推荐新手' + colors.reset);
console.log(` 1. 访问: ${colors.cyan}https://nodejs.org/${colors.reset}`);
console.log(` 2. 下载 LTS 版本(推荐 ${colors.cyan}v20.x${colors.reset}`);
console.log(` 3. 运行安装包,按向导完成安装`);
console.log(` 4. 安装完成后,${colors.yellow}重新打开终端${colors.reset}并再次运行此脚本`);
console.log('\n' + colors.yellow + '方式二使用 nvm-windows推荐开发者' + colors.reset);
console.log(` 1. 下载安装 nvm-windows: ${colors.cyan}https://github.com/coreybutler/nvm-windows/releases${colors.reset}`);
console.log(` 2. 打开新的 PowerShell 或 CMD 窗口`);
console.log(` 3. 执行: ${colors.cyan}nvm install 20${colors.reset}`);
console.log(` 4. 执行: ${colors.cyan}nvm use 20${colors.reset}`);
console.log(` 5. 验证: ${colors.cyan}node -v${colors.reset}`);
console.log('\n' + colors.yellow + '方式三使用 WingetWindows 10/11 自带' + colors.reset);
console.log(` ${colors.cyan}winget install OpenJS.NodeJS.LTS${colors.reset}`);
} else if (isMac) {
// macOS
console.log('\n' + colors.green + 'macOS 安装方式' + colors.reset);
console.log('\n' + colors.yellow + '方式一使用 Homebrew推荐' + colors.reset);
console.log(` ${colors.cyan}brew install node@${colors.reset}`);
console.log('\n' + colors.yellow + '方式二使用官方安装包' + colors.reset);
console.log(` 1. 访问: ${colors.cyan}https://nodejs.org/${colors.reset}`);
console.log(` 2. 下载 macOS 安装包(.pkg`);
console.log(` 3. 双击安装包按向导完成安装`);
console.log('\n' + colors.yellow + '方式三使用 nvm推荐开发者' + colors.reset);
console.log(` ${colors.cyan}# 1. 安装 nvm${colors.reset}`);
console.log(` curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash`);
console.log(` ${colors.cyan}# 2. 重新加载 shell 配置${colors.reset}`);
console.log(` source ~/.bashrc # 或 source ~/.zshrc`);
console.log(` ${colors.cyan}# 3. 安装 Node.js 20${colors.reset}`);
console.log(` nvm install 20`);
console.log(` nvm use 20`);
}
// 通用验证命令
console.log('\n' + colors.green + '验证安装' + colors.reset);
console.log(` 安装完成后,运行以下命令验证:`);
console.log(` ${colors.cyan}node -v${colors.reset} # 应显示 v20.x.x 或更高版本`);
console.log(` ${colors.cyan}npm -v${colors.reset} # 应显示 10.x.x 或更高版本`);
console.log('\n' + colors.gray + '安装完成后请重新运行此部署脚本' + colors.reset);
}
/**
* Linux 系统自动安装 Node.js
*
* 根据检测到的发行版,使用对应的方式自动安装 Node.js 20.x
*
* @returns {Promise<boolean>} 安装是否成功
*/
async function autoInstallNodeLinux() {
const distro = detectLinuxDistro();
log.step('自动安装 Node.js');
log.info(`检测到 Linux 发行版: ${distro}`);
try {
switch (distro) {
case 'ubuntu':
case 'debian':
log.info('使用 NodeSource 源安装 Node.js 20.x...');
log.info('执行: curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -');
const debianSetup = runCommand('curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -');
if (!debianSetup.success) {
log.error('NodeSource 源配置失败');
return false;
}
log.info('执行: sudo apt-get install -y nodejs');
const debianInstall = runCommand('sudo apt-get install -y nodejs');
if (!debianInstall.success) {
log.error('Node.js 安装失败');
return false;
}
break;
case 'centos':
case 'rhel':
case 'fedora':
log.info('使用 NodeSource 源安装 Node.js 20.x...');
log.info('执行: curl -fsSL https://rpm.nodesource.com/setup_20.x | sudo bash -');
const rpmSetup = runCommand('curl -fsSL https://rpm.nodesource.com/setup_20.x | sudo bash -');
if (!rpmSetup.success) {
log.error('NodeSource 源配置失败');
return false;
}
log.info('执行: sudo yum install -y nodejs');
const rpmInstall = runCommand('sudo yum install -y nodejs');
if (!rpmInstall.success) {
log.error('Node.js 安装失败');
return false;
}
break;
case 'arch':
log.info('执行: sudo pacman -S nodejs npm');
const archInstall = runCommand('sudo pacman -S --noconfirm nodejs npm');
if (!archInstall.success) {
log.error('Node.js 安装失败');
return false;
}
break;
default:
// 未知发行版,尝试使用 nvm 安装
log.info('未知发行版,尝试使用 nvm 安装...');
return await installNodeViaNvm();
}
// 验证安装
const checkResult = runCommand('node -v', { silent: true });
if (checkResult.success) {
log.success(`Node.js 安装成功: ${checkResult.output.trim()}`);
const npmCheck = runCommand('npm -v', { silent: true });
if (npmCheck.success) {
log.success(`npm 安装成功: ${npmCheck.output.trim()}`);
}
return true;
} else {
log.error('Node.js 安装后验证失败');
return false;
}
} catch (error) {
log.error(`自动安装失败: ${error.message}`);
return false;
}
}
/**
* 使用 nvm 安装 Node.js
*
* 适用于所有 Linux 发行版,作为通用安装方式
*
* @returns {Promise<boolean>} 安装是否成功
*/
async function installNodeViaNvm() {
log.info('使用 nvm 安装 Node.js...');
try {
// 1. 检查 nvm 是否已安装
const nvmCheck = runCommand('source ~/.nvm/nvm.sh && nvm --version', { silent: true });
if (!nvmCheck.success) {
// 安装 nvm
log.info('安装 nvm...');
const nvmInstall = runCommand('curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash');
if (!nvmInstall.success) {
log.error('nvm 安装失败');
return false;
}
}
// 2. 使用 nvm 安装 Node.js 20
log.info('使用 nvm 安装 Node.js 20...');
const nodeInstall = runCommand('export NVM_DIR="$HOME/.nvm" && [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" && nvm install 20');
if (!nodeInstall.success) {
log.error('Node.js 安装失败');
return false;
}
// 3. 设置默认版本
log.info('设置 Node.js 20 为默认版本...');
runCommand('export NVM_DIR="$HOME/.nvm" && [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" && nvm alias default 20', { silent: true });
log.success('Node.js 安装成功');
return true;
} catch (error) {
log.error(`nvm 安装失败: ${error.message}`);
return false;
}
}
/**
* 处理 Node.js 未安装的情况(Linux 交互式)
*
* @returns {Promise<boolean>} 是否继续部署
*/
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} 显示手动安装指引,退出脚本`);
const choice = await ask('请选择', '1');
if (choice === '1') {
const installed = await autoInstallNodeLinux();
if (installed) {
log.success('Node.js 和 npm 安装完成!');
log.info('请重新运行此脚本以继续部署');
console.log(colors.cyan + ' node install.js' + colors.reset);
// 询问是否立即重新运行
const rerun = await ask('\n是否立即重新运行部署脚本? (Y/n)', 'Y');
if (rerun.toLowerCase() === 'y') {
// 重新检查环境
return true;
}
} else {
log.error('自动安装失败,请尝试手动安装');
showNodeInstallGuide();
}
process.exit(1);
} else {
showNodeInstallGuide();
process.exit(1);
}
}
/**
* 环境检测主函数
*
* 检测内容:
* - Node.js 是否安装及版本(要求 >= 14.0.0
* - npm 是否安装
* - PM2 是否安装(未安装则自动安装)
* - Nginx 是否安装(可选)
*
* Linux 系统支持自动安装 Node.js
*/
async function checkEnvironment() {
log.step('环境检测');
// 1. 检测 Node.js 和 npm
const { nodeInstalled, npmInstalled, version } = checkNodeAndNpm();
const isLinux = process.platform === 'linux';
if (!nodeInstalled) {
if (isLinux) {
// Linux 系统:交互式询问是否自动安装
const shouldContinue = await handleNodeNotInstalledLinux();
if (shouldContinue) {
// 重新检测
return await checkEnvironment();
}
} else {
// 非 Linux 系统:显示安装指引
log.error('未检测到 Node.js,请先安装');
showNodeInstallGuide();
process.exit(1);
}
}
if (!npmInstalled) {
if (isLinux && nodeInstalled) {
// Linux 下 npm 未安装但 node 已安装,尝试重新安装 node
log.warning('未检测到 npm,尝试修复...');
const fixed = await autoInstallNodeLinux();
if (!fixed) {
log.error('npm 修复失败,请手动安装');
showNodeInstallGuide();
process.exit(1);
}
} else {
log.error('未检测到 npm,请重新安装 Node.jsnpm 会随 Node.js 一起安装)');
showNodeInstallGuide();
process.exit(1);
}
}
// 2. 检查 Node.js 版本
if (checkNodeVersion()) {
log.success(`Node.js ${version}`);
} else {
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} 显示手动升级指引,退出脚本`);
const upgradeChoice = await ask('请选择', '1');
if (upgradeChoice === '1') {
const upgraded = await autoInstallNodeLinux();
if (upgraded) {
log.success('Node.js 升级完成!请重新运行此脚本');
} else {
log.error('自动升级失败');
showNodeInstallGuide();
}
} else {
console.log('\n' + colors.yellow + '建议升级到 Node.js 20 LTS 版本:' + colors.reset);
showNodeInstallGuide();
}
} else {
console.log('\n' + colors.yellow + '建议升级到 Node.js 20 LTS 版本:' + colors.reset);
showNodeInstallGuide();
}
process.exit(1);
}
// 3. 检测 npm 版本
const npmResult = runCommand('npm --version', { silent: true });
if (npmResult.success) {
log.success(`npm ${npmResult.output.trim()}`);
}
// 4. 检测 PM2(进程管理器)
if (commandExists('pm2')) {
log.success('PM2 已安装');
} else {
log.warning('未检测到 PM2,将自动安装');
const result = runCommand('npm install -g pm2');
if (result.success) {
log.success('PM2 安装完成');
} else {
log.error('PM2 安装失败,请手动执行: npm install -g pm2');
}
}
// 5. 检测 Nginx(可选,仅在使用 Nginx 部署时需要)
if (commandExists('nginx') || commandExists('nginx.exe')) {
log.success('Nginx 已安装');
} else {
log.warning('未检测到 Nginx(可选,仅在使用Nginx部署时需要)');
}
log.divider();
}
// =============================================================================
// 配置函数
// =============================================================================
/**
* 数据库配置函数
*
* 交互流程:
* 1. 选择数据库类型(SQLite / MySQL
* 2. 如选择 MySQL,提示输入连接参数或自动安装
*/
async function configureDatabase(cmdArgs) {
log.step('数据库配置');
if (cmdArgs?.nonInteractive && cmdArgs?.dbType) {
config.dbType = cmdArgs.dbType;
log.info(`使用命令行参数: 数据库类型 = ${config.dbType}`);
} else if (cmdArgs?.nonInteractive) {
config.dbType = 'sqlite';
log.info('使用默认配置: 数据库类型 = sqlite');
} else {
config.dbType = await select('选择数据库类型:', [
{ label: 'SQLite(零配置,适合开发/小规模)', value: 'sqlite' },
{ label: 'MySQL(生产环境推荐)', value: 'mysql' }
]);
}
if (config.dbType === 'mysql') {
if (cmdArgs?.nonInteractive) {
config.dbConfig.host = process.env.MYSQL_HOST || 'localhost';
config.dbConfig.port = process.env.MYSQL_PORT || '3306';
config.dbConfig.username = process.env.MYSQL_USER || 'root';
config.dbConfig.password = process.env.MYSQL_PASSWORD || '';
config.dbConfig.database = process.env.MYSQL_DATABASE || 'idc_management';
log.info(`MySQL 配置: ${config.dbConfig.host}:${config.dbConfig.port}/${config.dbConfig.database}`);
} else {
const mysqlOption = await select('MySQL 配置方式:', [
{ label: '已有数据库(填写现有 MySQL 连接信息)', value: 'existing' },
{ label: '自动安装 MySQLLinux 系统支持自动安装配置)', value: 'install' }
]);
if (mysqlOption === 'install') {
const installResult = await installMySQL();
if (installResult.success) {
config.dbConfig = { ...installResult.config };
} else {
log.error('MySQL 自动安装失败');
const fallback = await ask('是否改为手动配置现有 MySQL? (Y/n)', 'Y');
if (fallback.toLowerCase() === 'y') {
await configureExistingMySQL();
} else {
log.info('已取消 MySQL 配置,将使用 SQLite');
config.dbType = 'sqlite';
}
}
} else {
await configureExistingMySQL();
}
}
if (config.dbType === 'mysql') {
const testResult = await testMySQLConnection();
if (!testResult.success) {
log.error(`MySQL 连接测试失败: ${testResult.error}`);
const retry = cmdArgs?.nonInteractive ? false : await ask('是否重新配置? (Y/n)', 'Y');
if (retry.toLowerCase() === 'y') {
return await configureDatabase(cmdArgs);
}
log.warning('将继续部署,但数据库可能无法正常工作');
}
}
}
log.divider();
}
async function configureExistingMySQL() {
console.log('\n' + colors.yellow + '请确保 MySQL 服务已启动并创建了数据库' + colors.reset);
console.log(colors.gray + '创建数据库命令: CREATE DATABASE idc_management CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;' + colors.reset + '\n');
config.dbConfig.host = await ask('MySQL 主机地址', 'localhost');
config.dbConfig.port = await ask('MySQL 端口', '3306');
config.dbConfig.username = await ask('MySQL 用户名', 'root');
config.dbConfig.password = await askPassword('MySQL 密码');
config.dbConfig.database = await ask('数据库名称', 'idc_management');
}
async function installMySQL() {
log.subStep('检测系统环境...');
const platform = process.platform;
if (platform === 'win32') {
log.warning('Windows 系统暂不支持自动安装 MySQL');
console.log('\n' + colors.cyan + '请手动安装 MySQL' + colors.reset);
console.log(' 1. 下载 MySQL: https://dev.mysql.com/downloads/mysql/');
console.log(' 2. 或使用 XAMPP/WAMP 等集成环境');
console.log(' 3. 安装后创建数据库:');
console.log(colors.gray + ' CREATE DATABASE idc_management CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;' + colors.reset);
return { success: false };
}
if (platform !== 'linux') {
log.warning('当前系统暂不支持自动安装 MySQL');
return { success: false };
}
const distro = detectLinuxDistro();
log.info(`检测到 Linux 发行版: ${distro}`);
const mysqlRootPassword = await askPassword('设置 MySQL root 密码(用于新安装的 MySQL');
if (!mysqlRootPassword) {
log.error('密码不能为空');
return { success: false };
}
log.subStep('开始安装 MySQL...');
try {
switch (distro) {
case 'ubuntu':
case 'debian':
await installMySQLDebian(mysqlRootPassword);
break;
case 'centos':
case 'rhel':
case 'rocky':
case 'almalinux':
await installMySQLRHEL(mysqlRootPassword);
break;
default:
log.warning(`暂不支持 ${distro} 自动安装 MySQL`);
return { success: false };
}
config.dbConfig.host = 'localhost';
config.dbConfig.port = '3306';
config.dbConfig.username = 'root';
config.dbConfig.password = mysqlRootPassword;
config.dbConfig.database = 'idc_management';
log.subStep('创建数据库...');
await createMySQLDatabase(mysqlRootPassword);
log.success('MySQL 安装配置完成');
return {
success: true,
config: {
host: 'localhost',
port: '3306',
username: 'root',
password: mysqlRootPassword,
database: 'idc_management'
}
};
} catch (error) {
log.error(`MySQL 安装失败: ${error.message}`);
return { success: false, error: error.message };
}
}
function detectLinuxDistro() {
try {
if (fs.existsSync('/etc/os-release')) {
const content = fs.readFileSync('/etc/os-release', 'utf8');
const idMatch = content.match(/^ID=(.*)$/m);
if (idMatch) {
const id = idMatch[1].replace(/"/g, '').toLowerCase();
if (['ubuntu', 'debian'].includes(id)) return 'ubuntu';
if (['centos', 'rhel', 'rocky', 'almalinux', 'fedora'].includes(id)) return 'centos';
return id;
}
}
if (fs.existsSync('/etc/redhat-release')) return 'centos';
if (fs.existsSync('/etc/debian_version')) return 'ubuntu';
} catch {}
return 'unknown';
}
async function installMySQLDebian(rootPassword) {
log.info('安装 MySQL (Ubuntu/Debian)...');
execSync('export DEBIAN_FRONTEND=noninteractive', { shell: '/bin/bash' });
execSync('apt-get update -qq', { shell: '/bin/bash', stdio: 'inherit' });
execSync(`debconf-set-selections <<< "mysql-server mysql-server/root_password password ${rootPassword}"`, { shell: '/bin/bash' });
execSync(`debconf-set-selections <<< "mysql-server mysql-server/root_password_again password ${rootPassword}"`, { shell: '/bin/bash' });
execSync('apt-get install -y -qq mysql-server', { shell: '/bin/bash', stdio: 'inherit' });
execSync('systemctl start mysql', { shell: '/bin/bash' });
execSync('systemctl enable mysql', { shell: '/bin/bash' });
log.success('MySQL 安装完成');
}
async function installMySQLRHEL(rootPassword) {
log.info('安装 MySQL (CentOS/RHEL)...');
execSync('yum install -y -q epel-release', { shell: '/bin/bash', stdio: 'inherit' });
execSync('yum install -y -q mysql-server', { shell: '/bin/bash', stdio: 'inherit' });
execSync('systemctl start mysqld', { shell: '/bin/bash' });
execSync('systemctl enable mysqld', { shell: '/bin/bash' });
try {
execSync(`mysqladmin -u root password "${rootPassword}"`, { shell: '/bin/bash' });
} catch {
log.info('root 密码可能已设置,跳过');
}
log.success('MySQL 安装完成');
}
async function createMySQLDatabase(rootPassword) {
log.subStep('创建数据库...');
const createDbSql = 'CREATE DATABASE IF NOT EXISTS idc_management CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;';
try {
execSync(`mysql -u root -p'${rootPassword}' -e "${createDbSql}"`, {
shell: '/bin/bash',
stdio: ['pipe', 'pipe', 'pipe']
});
log.success('数据库 idc_management 创建成功');
return;
} catch (error) {
const errorMsg = error.stderr ? error.stderr.toString() : error.message;
if (errorMsg.includes('Access denied') || errorMsg.includes('ERROR 1045')) {
log.warning('MySQL root 密码验证失败,尝试无密码连接...');
try {
execSync(`mysql -u root -e "${createDbSql}"`, {
shell: '/bin/bash',
stdio: ['pipe', 'pipe', 'pipe']
});
log.success('数据库 idc_management 创建成功');
return;
} catch (e2) {
log.warning(`无密码连接也失败: ${e2.message}`);
}
}
log.warning(`数据库自动创建失败: ${errorMsg.trim()}`);
log.info('请手动创建数据库:');
console.log(colors.cyan + ' mysql -u root -p' + colors.reset);
console.log(colors.cyan + ' CREATE DATABASE idc_management CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;' + colors.reset);
}
}
async function testMySQLConnection() {
log.subStep('测试 MySQL 连接...');
try {
execSync(`mysql -h ${config.dbConfig.host} -P ${config.dbConfig.port} -u ${config.dbConfig.username} -p'${config.dbConfig.password}' -e "SELECT 1" ${config.dbConfig.database}`, {
shell: '/bin/bash',
stdio: ['pipe', 'pipe', 'pipe'],
timeout: 5000
});
log.success('MySQL 连接测试成功');
return { success: true };
} catch (error) {
const errorMsg = error.stderr ? error.stderr.toString() : error.message;
if (errorMsg.includes('Access denied') || errorMsg.includes('ERROR 1045')) {
return { success: false, error: '用户名或密码错误' };
}
if (errorMsg.includes('Unknown database')) {
return { success: false, error: `数据库 ${config.dbConfig.database} 不存在` };
}
if (errorMsg.includes('Connection refused') || errorMsg.includes("Can't connect")) {
return { success: false, error: `无法连接到 ${config.dbConfig.host}:${config.dbConfig.port}` };
}
return { success: false, error: errorMsg.trim() || '连接失败' };
}
}
/**
* 服务配置函数
*
* 交互流程:
* 1. 配置后端端口
* 2. 选择运行环境(development / production
* 3. 选择前端部署方式(Nginx / PM2 serve
* 4. 如选择 Nginx 但未安装,提供安装选项
* 5. 配置前端端口和域名
*/
async function configureServices(cmdArgs) {
log.step('服务配置');
if (cmdArgs?.nonInteractive && cmdArgs?.backendPort) {
config.backendPort = cmdArgs.backendPort;
log.info(`使用命令行参数: 后端端口 = ${config.backendPort}`);
} else if (cmdArgs?.nonInteractive) {
config.backendPort = '8000';
log.info('使用默认配置: 后端端口 = 8000');
} else {
config.backendPort = await ask('后端服务端口', '8000');
}
if (cmdArgs?.nonInteractive) {
config.nodeEnv = 'production';
log.info('使用默认配置: 运行环境 = production');
} else {
config.nodeEnv = await select('选择运行环境:', [
{ label: 'production(生产模式,性能优化,推荐正式部署)', value: 'production' },
{ label: 'development(开发模式,详细日志,便于调试)', value: 'development' }
]);
}
if (cmdArgs?.nonInteractive || cmdArgs?.skipNginx) {
config.frontendDeploy = 'pm2';
config.frontendPort = '3000';
log.info('使用默认配置: 前端部署 = PM2 serve (端口 3000)');
} else {
config.frontendDeploy = await select('前端部署方式:', [
{ label: 'Nginx(性能最优,推荐生产环境)', value: 'nginx' },
{ label: 'PM2 serve(简单快捷,无需额外安装)', value: 'pm2' }
]);
}
if (config.frontendDeploy === 'nginx') {
const nginxInstalled = isNginxInstalled();
if (!nginxInstalled) {
log.warning('未检测到 Nginx 安装');
if (process.platform === 'win32') {
if (!cmdArgs?.nonInteractive) {
const autoInstall = await ask('是否自动下载并安装 Nginx? (Y/n)', 'Y');
if (autoInstall.toLowerCase() === 'y') {
await autoInstallNginx();
} else {
log.info('已跳过自动安装,请手动安装 Nginx 后启动服务');
console.log(` 下载地址: ${colors.cyan}https://nginx.org/en/download.html${colors.reset}`);
console.log(` 安装路径建议: ${colors.cyan}C:/nginx${colors.reset}`);
}
}
} else if (process.platform === 'linux') {
if (!cmdArgs?.nonInteractive) {
const autoInstall = await ask('是否自动安装 Nginx? (Y/n)', 'Y');
if (autoInstall.toLowerCase() === 'y') {
const installed = await autoInstallNginxLinux();
if (!installed) {
log.error('Nginx 自动安装失败');
log.info('请手动安装后重新运行脚本');
showNginxInstallGuideLinux();
process.exit(1);
}
} else {
log.info('已跳过自动安装');
showNginxInstallGuideLinux();
const continueDeploy = await ask('是否继续部署(后端将启动,前端需手动配置 Nginx? (Y/n)', 'Y');
if (continueDeploy.toLowerCase() !== 'y') {
log.info('已取消部署,请安装 Nginx 后重新运行');
process.exit(0);
}
}
}
} else {
log.info('请使用 Homebrew 安装 Nginx');
console.log(` ${colors.cyan}brew install nginx${colors.reset}`);
}
}
if (!cmdArgs?.nonInteractive) {
config.frontendPort = await ask('Nginx 监听端口', '80');
config.domain = await ask('域名(没有则填localhost', 'localhost');
} else {
config.frontendPort = '80';
config.domain = 'localhost';
}
} else {
if (!cmdArgs?.nonInteractive) {
config.frontendPort = await ask('前端服务端口', '3000');
}
}
log.divider();
}
/**
* 配置确认函数
*
* 显示所有配置参数,等待用户确认后开始部署
*/
async function confirmConfiguration() {
log.step('配置确认');
// 显示配置摘要
console.log('\n' + colors.bright + '部署配置摘要:' + colors.reset);
console.log(` 数据库类型: ${colors.cyan}${config.dbType}${colors.reset}`);
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}`);
}
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}`);
if (config.frontendDeploy === 'nginx') {
console.log(` 域名: ${colors.cyan}${config.domain}${colors.reset}`);
}
// 等待用户确认
const confirm = await ask('\n确认以上配置并开始部署? (Y/n)', 'Y');
if (confirm.toLowerCase() !== 'y') {
log.info('已取消部署');
process.exit(0);
}
log.divider();
}
// =============================================================================
// Nginx 安装指引函数(Linux
// =============================================================================
/**
* 显示 Linux Nginx 安装指引
* 根据是否 root 用户显示相应的命令
*/
function showNginxInstallGuideLinux() {
const distro = detectLinuxDistro();
const root = isRootUser();
const sudoPrefix = root ? '' : 'sudo ';
console.log('\n' + colors.bright + 'Nginx 安装命令:' + colors.reset);
if (root) {
console.log(colors.gray + '(当前以 root 用户运行,无需 sudo' + colors.reset);
}
switch (distro) {
case 'ubuntu':
case 'debian':
console.log(` ${colors.cyan}${sudoPrefix}apt update${colors.reset}`);
console.log(` ${colors.cyan}${sudoPrefix}apt install -y nginx${colors.reset}`);
console.log(` ${colors.cyan}${sudoPrefix}systemctl start nginx${colors.reset}`);
console.log(` ${colors.cyan}${sudoPrefix}systemctl enable nginx${colors.reset}`);
break;
case 'centos':
case 'rhel':
case 'fedora':
console.log(` ${colors.cyan}${sudoPrefix}yum install -y epel-release${colors.reset}`);
console.log(` ${colors.cyan}${sudoPrefix}yum install -y nginx${colors.reset}`);
console.log(` ${colors.cyan}${sudoPrefix}systemctl start nginx${colors.reset}`);
console.log(` ${colors.cyan}${sudoPrefix}systemctl enable nginx${colors.reset}`);
break;
case 'arch':
console.log(` ${colors.cyan}${sudoPrefix}pacman -S --noconfirm nginx${colors.reset}`);
console.log(` ${colors.cyan}${sudoPrefix}systemctl start nginx${colors.reset}`);
console.log(` ${colors.cyan}${sudoPrefix}systemctl enable nginx${colors.reset}`);
break;
default:
console.log(` ${colors.cyan}# Ubuntu/Debian${colors.reset}`);
console.log(` ${sudoPrefix}apt update && ${sudoPrefix}apt install -y nginx`);
console.log(`\n ${colors.cyan}# CentOS/RHEL/Fedora${colors.reset}`);
console.log(` ${sudoPrefix}yum install -y epel-release && ${sudoPrefix}yum install -y nginx`);
console.log(`\n ${colors.cyan}# Arch Linux${colors.reset}`);
console.log(` ${sudoPrefix}pacman -S nginx`);
}
console.log('\n' + colors.gray + '安装完成后,请重新运行此部署脚本。' + colors.reset);
}
/**
* 检测是否以 root 用户运行
*
* @returns {boolean} 是否 root 用户
*/
function isRootUser() {
return process.getuid && process.getuid() === 0;
}
/**
* Linux 系统自动安装 Nginx
*
* 根据检测到的发行版,使用包管理器自动安装 Nginx
* 自动检测是否以 root 用户运行,决定是否使用 sudo
*
* @returns {Promise<boolean>} 安装是否成功
*/
async function autoInstallNginxLinux() {
const distro = detectLinuxDistro();
const root = isRootUser();
const sudoPrefix = root ? '' : 'sudo ';
log.step('自动安装 Nginx');
log.info(`检测到 Linux 发行版: ${distro}`);
if (root) {
log.info('当前以 root 用户运行,无需 sudo');
}
try {
let installCommand = '';
let startCommand = `${sudoPrefix}systemctl start nginx`;
let enableCommand = `${sudoPrefix}systemctl enable nginx`;
switch (distro) {
case 'ubuntu':
case 'debian':
log.info('使用 apt 安装 Nginx...');
log.info(`执行: ${sudoPrefix}apt update`);
const updateResult = runCommand(`${sudoPrefix}apt update`);
if (!updateResult.success) {
log.warning('apt update 失败,尝试继续安装...');
}
log.info(`执行: ${sudoPrefix}apt install -y nginx`);
installCommand = `${sudoPrefix}apt install -y nginx`;
break;
case 'centos':
case 'rhel':
case 'fedora':
log.info('使用 yum 安装 Nginx...');
log.info(`执行: ${sudoPrefix}yum install -y epel-release`);
const epelResult = runCommand(`${sudoPrefix}yum install -y epel-release`);
if (!epelResult.success) {
log.warning('epel-release 安装失败,尝试继续...');
}
log.info(`执行: ${sudoPrefix}yum install -y nginx`);
installCommand = `${sudoPrefix}yum install -y nginx`;
break;
case 'arch':
log.info('使用 pacman 安装 Nginx...');
log.info(`执行: ${sudoPrefix}pacman -S --noconfirm nginx`);
installCommand = `${sudoPrefix}pacman -S --noconfirm nginx`;
break;
default:
log.error('未知的 Linux 发行版,无法自动安装');
return false;
}
// 执行安装
const installResult = runCommand(installCommand);
if (!installResult.success) {
log.error('Nginx 安装失败');
return false;
}
log.success('Nginx 安装完成');
// 启动 Nginx
log.info('启动 Nginx 服务...');
const startResult = runCommand(startCommand);
if (!startResult.success) {
log.warning('Nginx 启动失败,可能需要手动启动');
} else {
log.success('Nginx 服务已启动');
}
// 设置开机自启
log.info('设置开机自启...');
runCommand(enableCommand, { silent: true });
// 验证安装
const checkResult = runCommand('nginx -v', { silent: true });
if (checkResult.success) {
log.success(`Nginx 安装成功: ${checkResult.output.trim()}`);
return true;
} else {
log.error('Nginx 安装验证失败');
return false;
}
} catch (error) {
log.error(`自动安装失败: ${error.message}`);
return false;
}
}
// =============================================================================
// Nginx 自动安装函数(Windows
// =============================================================================
/**
* Windows 系统自动下载并安装 Nginx
*
* 安装流程:
* 1. 从 nginx.org 下载稳定版压缩包
* 2. 解压到 C:\nginx
* 3. 输出后续配置指引
*/
async function autoInstallNginx() {
log.step('自动安装 Nginx');
log.info('正在下载 Nginx...');
// 引入所需模块
const https = require('https');
// 配置参数
const nginxUrl = 'https://nginx.org/download/nginx-1.24.0.zip'; // Nginx 稳定版下载地址
const downloadPath = path.join(__dirname, 'nginx-1.24.0.zip'); // 临时下载路径
const installPath = 'C:\\nginx'; // 安装路径
try {
// 1. 下载 Nginx 压缩包
await new Promise((resolve, reject) => {
const file = fs.createWriteStream(downloadPath);
https.get(nginxUrl, (response) => {
response.pipe(file);
file.on('finish', () => {
file.close();
resolve();
});
}).on('error', reject);
});
log.success('Nginx 下载完成');
// 2. 解压到 C:\
log.info('正在解压...');
execSync(`powershell -Command "Expand-Archive -Path '${downloadPath}' -DestinationPath 'C:\\\\' -Force"`, { stdio: 'inherit' });
// 3. 重命名文件夹为 nginx
if (fs.existsSync('C:\\nginx-1.24.0')) {
if (fs.existsSync(installPath)) {
fs.rmSync(installPath, { recursive: true }); // 删除旧版本
}
fs.renameSync('C:\\nginx-1.24.0', installPath);
}
// 4. 清理临时文件
fs.unlinkSync(downloadPath);
// 5. 输出安装成功信息和后续步骤
log.success(`Nginx 已安装到 ${installPath}`);
log.info('安装后操作:');
console.log(` 1. 将配置文件复制到: ${colors.cyan}C:/nginx/conf/conf.d/idc.conf${colors.reset}`);
console.log(` 2. 修改主配置: 在 C:/nginx/conf/nginx.conf 的 http 段添加: ${colors.cyan}include conf.d/*.conf;${colors.reset}`);
console.log(` 3. 启动 Nginx: ${colors.cyan}C:/nginx/nginx.exe${colors.reset}`);
} catch (error) {
log.error('Nginx 自动安装失败: ' + error.message);
log.info('请手动下载安装: https://nginx.org/en/download.html');
throw error;
}
}
// =============================================================================
// 配置文件生成函数
// =============================================================================
/**
* 生成后端环境变量文件 (.env)
*
* 根据用户选择的配置生成后端所需的 .env 文件
*/
function generateBackendEnv() {
const envPath = path.join(__dirname, 'backend', '.env');
2026-03-11 14:28:00 +08:00
const jwtSecret = generateSecretKey(64);
let envContent = `# IDC设备管理系统 - 环境配置
# 生成时间: ${new Date().toISOString()}
# ==============================================
# 服务器配置
# ==============================================
# 服务器端口
PORT=${config.backendPort}
# 运行环境: development 或 production
NODE_ENV=${config.nodeEnv}
2026-03-11 14:28:00 +08:00
# ==============================================
# 安全配置
# ==============================================
# JWT 密钥(自动生成,请妥善保管)
JWT_SECRET=${jwtSecret}
# ==============================================
# 数据库配置
# ==============================================
# 数据库类型: sqlite 或 mysql
DB_TYPE=${config.dbType}
`;
if (config.dbType === 'sqlite') {
envContent += `
# SQLite 配置
DB_PATH=./idc_management.db
`;
} else {
envContent += `
# MySQL 配置
MYSQL_HOST=${config.dbConfig.host}
MYSQL_PORT=${config.dbConfig.port}
MYSQL_USERNAME=${config.dbConfig.username}
MYSQL_PASSWORD=${config.dbConfig.password}
MYSQL_DATABASE=${config.dbConfig.database}
`;
}
fs.writeFileSync(envPath, envContent);
log.success('后端环境变量文件已生成 (.env)');
2026-03-11 14:28:00 +08:00
log.info(`JWT_SECRET 已自动生成 (${jwtSecret.length}位)`);
}
/**
* 生成 PM2 配置文件
*
* 生成 ecosystem.config.js 用于 PM2 进程管理
*/
function generatePM2Config() {
// 创建 deploy 目录
const deployDir = path.join(__dirname, 'deploy');
if (!fs.existsSync(deployDir)) {
fs.mkdirSync(deployDir, { recursive: true });
}
// 构建 PM2 配置对象
const pm2Config = {
apps: [{
name: 'idc-backend', // 应用名称
script: './server.js', // 启动脚本
cwd: path.join(__dirname, 'backend'), // 工作目录
instances: 1, // 实例数
exec_mode: 'fork', // 执行模式
env: {
NODE_ENV: 'production',
PORT: config.backendPort
},
max_memory_restart: '1G', // 内存限制
log_file: path.join(__dirname, 'backend', 'logs', 'combined.log'),
out_file: path.join(__dirname, 'backend', 'logs', 'out.log'),
error_file: path.join(__dirname, 'backend', 'logs', 'error.log'),
log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
merge_logs: true,
autorestart: true // 自动重启
}]
};
// 如果使用 PM2 serve 部署前端,添加前端应用配置
if (config.frontendDeploy === 'pm2') {
pm2Config.apps.push({
name: 'idc-frontend',
script: 'serve',
cwd: path.join(__dirname, 'frontend'),
args: `-s dist -l ${config.frontendPort}`, // -s: 静态文件目录, -l: 监听端口
instances: 1,
exec_mode: 'fork',
env: { NODE_ENV: 'production' },
max_memory_restart: '500M',
autorestart: true
});
}
// 写入配置文件
fs.writeFileSync(
path.join(deployDir, 'ecosystem.config.js'),
`module.exports = ${JSON.stringify(pm2Config, null, 2)};`
);
log.success('PM2 配置文件已生成 (deploy/ecosystem.config.js)');
}
/**
* 生成 Nginx 配置文件
*
* 生成 nginx-idc.conf 用于 Nginx 反向代理和静态文件服务
*/
function generateNginxConfig() {
if (config.frontendDeploy !== 'nginx') return;
// 创建 deploy 目录
const deployDir = path.join(__dirname, 'deploy');
if (!fs.existsSync(deployDir)) {
fs.mkdirSync(deployDir, { recursive: true });
}
const isWindows = process.platform === 'win32';
// 转换路径分隔符(Windows 反斜杠转斜杠)
const frontendPath = path.join(__dirname, 'frontend', 'dist').replace(/\\/g, '/');
// 构建 Nginx 配置内容
const nginxConfig = `# IDC设备管理系统 - Nginx配置
# 生成时间: ${new Date().toISOString()}
server {
listen ${config.frontendPort};
server_name ${config.domain};
# 前端静态文件目录
root "${frontendPath}";
index index.html;
# Gzip压缩
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css text/xml text/javascript application/javascript application/xml+rss application/json;
# 静态资源缓存(1年)
location ~* \\.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# 前端路由支持(SPA单页应用)
location / {
try_files $uri $uri/ /index.html;
}
# API代理到后端
location /api {
proxy_pass http://127.0.0.1:${config.backendPort};
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
# 文件上传代理
location /uploads {
proxy_pass http://127.0.0.1:${config.backendPort};
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
client_max_body_size 50M; # 最大上传文件大小
}
}
`;
// 写入配置文件
fs.writeFileSync(path.join(deployDir, 'nginx-idc.conf'), nginxConfig);
log.success('Nginx 配置文件已生成 (deploy/nginx-idc.conf)');
// 自动配置 NginxLinux 系统)
if (!isWindows && config.frontendDeploy === 'nginx') {
autoConfigureNginx();
}
// 输出平台特定的配置指引
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}`);
}
}
/**
* 自动配置 NginxLinux
*
* 自动复制配置文件到系统目录并启用
*/
function autoConfigureNginx() {
log.step('自动配置 Nginx');
const root = isRootUser();
const sudoPrefix = root ? '' : 'sudo ';
const configSource = path.join(__dirname, 'deploy', 'nginx-idc.conf');
try {
// 检测 Nginx 配置目录结构
let configDir = '';
let useSitesAvailable = false;
if (fs.existsSync('/etc/nginx/sites-available')) {
configDir = '/etc/nginx/sites-available';
useSitesAvailable = true;
} else if (fs.existsSync('/etc/nginx/conf.d')) {
configDir = '/etc/nginx/conf.d';
useSitesAvailable = false;
} else {
log.warning('未找到标准 Nginx 配置目录,请手动配置');
return;
}
// 复制配置文件
const configDest = path.join(configDir, 'idc');
log.info(`复制配置到 ${configDest}...`);
const copyResult = runCommand(`${sudoPrefix}cp "${configSource}" "${configDest}"`);
if (!copyResult.success) {
log.error('配置文件复制失败');
return;
}
// 如果使用 sites-available,创建软链接
if (useSitesAvailable) {
log.info('创建站点软链接...');
const linkResult = runCommand(`${sudoPrefix}ln -sf "${configDest}" /etc/nginx/sites-enabled/idc`);
if (!linkResult.success) {
log.warning('软链接创建失败');
}
// 删除默认配置(如果存在)
if (fs.existsSync('/etc/nginx/sites-enabled/default')) {
log.info('删除默认站点配置...');
runCommand(`${sudoPrefix}rm -f /etc/nginx/sites-enabled/default`, { silent: true });
}
}
// 测试配置
log.info('测试 Nginx 配置...');
const testResult = runCommand(`${sudoPrefix}nginx -t`);
if (!testResult.success) {
log.error('Nginx 配置测试失败');
return;
}
// 重载 Nginx
log.info('重载 Nginx 服务...');
const reloadResult = runCommand(`${sudoPrefix}nginx -s reload`);
if (reloadResult.success) {
log.success('Nginx 配置完成并已生效');
} else {
log.warning('Nginx 重载失败,尝试启动服务...');
runCommand(`${sudoPrefix}systemctl start nginx`, { silent: true });
}
} catch (error) {
log.error(`自动配置失败: ${error.message}`);
log.info('请手动配置 Nginx');
}
}
// =============================================================================
// 部署执行函数
// =============================================================================
/**
* 安装项目依赖
*
* 依次安装后端和前端的 npm 依赖
*/
async function installDependencies() {
log.step('安装依赖');
// 安装后端依赖
log.info('安装后端依赖...');
const backendResult = runCommand('npm install', { cwd: path.join(__dirname, 'backend') });
if (backendResult.success) {
log.success('后端依赖安装完成');
} else {
log.error('后端依赖安装失败');
throw new Error('Backend install failed');
}
// 安装前端依赖
log.info('安装前端依赖...');
const frontendResult = runCommand('npm install', { cwd: path.join(__dirname, 'frontend') });
if (frontendResult.success) {
log.success('前端依赖安装完成');
} else {
log.error('前端依赖安装失败');
throw new Error('Frontend install failed');
}
}
/**
* 初始化数据库
*
* 执行 backend/scripts/init-database.js 创建数据库表结构
*/
async function initDatabase() {
log.step('数据库初始化');
const initScript = path.join(__dirname, 'backend', 'scripts', 'init-database.js');
// 检查初始化脚本是否存在
if (!fs.existsSync(initScript)) {
log.warning('未找到数据库初始化脚本,跳过');
return;
}
log.info('正在初始化数据库...');
const result = runCommand('node scripts/init-database.js', {
cwd: path.join(__dirname, 'backend')
});
if (result.success) {
log.success('数据库初始化完成');
} else {
log.error('数据库初始化失败');
log.info('请检查数据库配置并手动运行: node backend/scripts/init-database.js');
}
}
/**
* 构建前端项目
*
* 执行 npm run build 生成生产环境静态文件
*/
async function buildFrontend() {
log.step('构建前端');
log.info('执行 npm run build...');
const result = runCommand('npm run build', { cwd: path.join(__dirname, 'frontend') });
if (result.success) {
log.success('前端构建完成');
} else {
log.error('前端构建失败');
throw new Error('Frontend build failed');
}
}
/**
* 启动服务
*
* 使用 PM2 启动后端和前端(PM2方式)服务
*/
async function startServices() {
log.step('启动服务');
// 确保日志目录存在
const logDir = path.join(__dirname, 'backend', 'logs');
if (!fs.existsSync(logDir)) {
fs.mkdirSync(logDir, { recursive: true });
}
// 停止已有服务(避免冲突)
log.info('停止已有服务...');
2026-03-11 14:28:00 +08:00
const stopCmd = process.platform === 'win32'
? 'pm2 stop idc-backend idc-frontend 2>nul || exit 0'
: 'pm2 stop idc-backend idc-frontend 2>/dev/null || true';
runCommand(stopCmd, { silent: true });
// 启动后端服务
log.info('启动后端服务...');
const backendResult = runCommand(
`pm2 start server.js --name "idc-backend" --env ${config.nodeEnv}`,
{ cwd: path.join(__dirname, 'backend') }
);
if (backendResult.success) {
log.success(`后端服务已启动 (端口: ${config.backendPort})`);
} else {
log.error('后端服务启动失败');
}
// 如果使用 PM2 serve,启动前端服务
if (config.frontendDeploy === 'pm2') {
log.info('安装 serve 包...');
runCommand('npm install -g serve', { silent: true });
log.info('启动前端服务...');
const frontendResult = runCommand(
`pm2 start serve --name "idc-frontend" -- -s dist -l ${config.frontendPort}`,
{ cwd: path.join(__dirname, 'frontend') }
);
if (frontendResult.success) {
log.success(`前端服务已启动 (端口: ${config.frontendPort})`);
} else {
log.error('前端服务启动失败');
}
}
// 保存 PM2 配置(开机自启)
runCommand('pm2 save', { silent: true });
log.divider();
}
// =============================================================================
// 输出函数
// =============================================================================
/**
* 输出部署完成后的管理命令和访问地址
*/
function printManagementCommands() {
log.step('部署完成!管理命令:');
// PM2 管理命令
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 logs idc-frontend${colors.reset} 查看前端日志(PM2方式)`);
console.log(` ${colors.cyan}pm2 restart idc-backend${colors.reset} 重启后端`);
console.log(` ${colors.cyan}pm2 stop idc-backend${colors.reset} 停止后端`);
console.log(` ${colors.cyan}pm2 delete idc-backend${colors.reset} 删除后端服务`);
// Nginx 管理命令(如适用)
if (config.frontendDeploy === 'nginx') {
console.log('\n' + colors.bright + 'Nginx 管理:' + colors.reset);
if (process.platform === 'win32') {
console.log(` ${colors.cyan}nginx -t${colors.reset} 测试配置`);
console.log(` ${colors.cyan}nginx -s reload${colors.reset} 重载配置`);
console.log(` ${colors.cyan}nginx -s stop${colors.reset} 停止Nginx`);
} else {
console.log(` ${colors.cyan}sudo nginx -t${colors.reset} 测试配置`);
console.log(` ${colors.cyan}sudo systemctl restart nginx${colors.reset} 重启Nginx`);
}
}
// 访问地址
console.log('\n' + colors.bright + '访问地址:' + colors.reset);
console.log(` 后端API: ${colors.cyan}http://localhost:${config.backendPort}/api${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('\n' + colors.bright + '更新升级:' + colors.reset);
console.log(` ${colors.cyan}npm run update${colors.reset} 一键更新(拉取代码+重建+重启)`);
log.divider();
log.success('安装部署完成!');
}
// =============================================================================
// 主函数
// =============================================================================
/**
* 主函数 - 脚本入口
*
* 执行流程:
* 1. 显示欢迎信息
* 2. 环境检测
* 3. 交互式配置(数据库、服务)
* 4. 配置确认
* 5. 生成配置文件
* 6. 安装依赖
* 7. 初始化数据库
* 8. 构建前端
* 9. 启动服务
* 10. 输出管理命令
*/
async function main() {
installStartTime = Date.now();
const cmdArgs = parseArgs();
if (cmdArgs.help) {
showHelp();
return;
}
initLogFile();
console.log(`
${colors.bright}${colors.cyan}
╔══════════════════════════════════════════════════════════╗
║ IDC设备管理系统 - 安装部署脚本 v${SCRIPT_VERSION}
║ Interactive Installation & Deployment Script ║
╚══════════════════════════════════════════════════════════╝
${colors.reset}`);
if (cmdArgs.nonInteractive) {
log.info('运行模式: 非交互式(使用默认配置)');
}
try {
await checkEnvironment();
await configureDatabase(cmdArgs);
await configureServices(cmdArgs);
if (!cmdArgs.nonInteractive) {
await confirmConfiguration();
}
generateBackendEnv();
generatePM2Config();
generateNginxConfig();
await installDependencies();
await initDatabase();
if (!cmdArgs.skipBuild) {
await buildFrontend();
} else {
log.info('已跳过前端构建');
}
await startServices();
await healthCheck();
printSummary();
} catch (error) {
log.error(`部署失败: ${error.message}`);
console.error(error);
process.exit(1);
} finally {
rl.close();
closeLogFile();
}
}
async function healthCheck() {
log.step('健康检查');
log.subStep('检查后端服务状态...');
const maxRetries = 5;
const retryDelay = 3000;
for (let i = 1; i <= maxRetries; i++) {
try {
const result = execSync('curl -s -o /dev/null -w "%{http_code}" http://localhost:' + config.backendPort + '/health 2>nul || echo "000"', {
encoding: 'utf8',
stdio: ['pipe', 'pipe', 'pipe'],
timeout: 5000
}).trim();
if (result === '200') {
log.success('后端服务健康检查通过');
return { success: true };
}
if (i < maxRetries) {
log.subStep(`第 ${i} 次检查失败 (HTTP ${result})${retryDelay/1000}秒后重试...`);
await new Promise(resolve => setTimeout(resolve, retryDelay));
}
} catch (error) {
if (i < maxRetries) {
log.subStep(`第 ${i} 次检查失败,${retryDelay/1000}秒后重试...`);
await new Promise(resolve => setTimeout(resolve, retryDelay));
}
}
}
log.warning('健康检查未通过,请手动验证服务状态');
return { success: false };
}
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}`);
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(`\n${colors.bright}更新升级:${colors.reset}`);
console.log(` ${colors.cyan}node update.js${colors.reset} 一键更新`);
log.divider();
log.success('安装部署完成!');
}
main();