Files
yunrui_asset/install.js
T
zhang1106 7ed71686b7 refactor(config): 优化Nginx配置文件生成逻辑
- 调整配置项顺序提升可读性
- 增强API代理和文件上传代理配置
- 完善Gzip压缩和静态资源缓存设置
- 修复SPA路由匹配规则
2026-05-09 09:37:17 +08:00

2114 lines
71 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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);
});
});
}
/**
* 密码输入函数 - 隐藏输入内容
*
* @param {string} question - 提示问题文本
* @returns {Promise<string>} 用户输入的密码
*
* 使用示例:
* const password = await askPassword('请输入密码');
*/
function askPassword(question) {
return new Promise((resolve) => {
const prompt = `${question}: `;
process.stdout.write(prompt);
let password = '';
const stdin = process.stdin;
const stdout = process.stdout;
const cleanup = () => {
if (stdin.isTTY) {
try {
stdin.setRawMode(false);
} catch {}
}
stdin.removeListener('data', onData);
};
const onData = (data) => {
const char = data.toString();
const code = char.charCodeAt(0);
if (code === 10 || code === 13) {
cleanup();
stdout.write('\n');
resolve(password);
} else if (code === 3) {
cleanup();
stdout.write('\n已取消\n');
process.exit(0);
} else if (code === 127 || code === 8) {
if (password.length > 0) {
password = password.slice(0, -1);
stdout.write('\b \b');
}
} else if (code >= 32) {
password += char;
stdout.write('*');
}
};
if (stdin.isTTY) {
stdin.setRawMode(true);
stdin.resume();
stdin.on('data', onData);
} else {
rl.question('', (answer) => {
resolve(answer.trim());
});
}
});
}
/**
* 选择函数 - 显示选项列表并获取用户选择
*
* @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;
}
/**
* 生成随机密钥
*
* @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');
const jwtSecret = generateSecretKey(64);
let envContent = `# IDC设备管理系统 - 环境配置
# 生成时间: ${new Date().toISOString()}
# ==============================================
# 服务器配置
# ==============================================
# 服务器端口
PORT=${config.backendPort}
# 运行环境: development 或 production
NODE_ENV=${config.nodeEnv}
# ==============================================
# 安全配置
# ==============================================
# 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)');
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';
const frontendPath = path.join(__dirname, 'frontend', 'dist').replace(/\\/g, '/');
const nginxConfig = `# IDC设备管理系统 - Nginx配置
# 生成时间: ${new Date().toISOString()}
server {
listen ${config.frontendPort};
server_name ${config.domain};
client_max_body_size 100M;
# 前端静态文件目录
root "${frontendPath}";
index index.html;
# Gzip压缩
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml text/javascript application/javascript application/xml+rss application/json application/x-javascript image/svg+xml;
# 静态资源缓存(1年)
location ~* \\.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot|hdr)$ {
expires 1y;
add_header Cache-Control "public, immutable";
try_files $uri =404;
}
# API代理到后端
location /api/ {
proxy_pass http://127.0.0.1:${config.backendPort}/api/;
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_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 300s;
proxy_buffering off;
}
# 文件上传代理
location /uploads/ {
proxy_pass http://127.0.0.1:${config.backendPort}/uploads/;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
client_max_body_size 100M;
}
# 前端路由支持(SPA单页应用,必须放在最后)
location / {
try_files $uri $uri/ /index.html =404;
}
}
`;
// 写入配置文件
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('停止已有服务...');
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();