feat(install): 修复一键部署脚本
This commit is contained in:
+82
-21
@@ -81,17 +81,6 @@ const log = {
|
||||
// 配置存储对象
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* 部署配置对象
|
||||
* 存储用户交互过程中设置的所有配置参数
|
||||
*
|
||||
* @property {string} dbType - 数据库类型:'sqlite' 或 'mysql'
|
||||
* @property {Object} dbConfig - MySQL 配置参数(当 dbType='mysql' 时使用)
|
||||
* @property {number} backendPort - 后端服务监听端口
|
||||
* @property {string} frontendDeploy - 前端部署方式:'nginx' 或 'pm2'
|
||||
* @property {number} frontendPort - 前端服务监听端口
|
||||
* @property {string} domain - 域名配置(Nginx 使用)
|
||||
*/
|
||||
/**
|
||||
* 部署配置对象
|
||||
* 存储用户交互过程中设置的所有配置参数
|
||||
@@ -139,15 +128,69 @@ const rl = readline.createInterface({
|
||||
*/
|
||||
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 wasRaw = stdin.isRaw;
|
||||
|
||||
if (stdin.isTTY) {
|
||||
stdin.setRawMode(true);
|
||||
}
|
||||
stdin.resume();
|
||||
stdin.setEncoding('utf8');
|
||||
|
||||
const onData = (char) => {
|
||||
const c = char;
|
||||
|
||||
switch (c) {
|
||||
case '\n':
|
||||
case '\r':
|
||||
case '\u0004':
|
||||
if (stdin.isTTY) {
|
||||
stdin.setRawMode(wasRaw || false);
|
||||
}
|
||||
stdin.pause();
|
||||
stdin.removeListener('data', onData);
|
||||
console.log();
|
||||
resolve(password);
|
||||
break;
|
||||
case '\u0003':
|
||||
process.exit();
|
||||
break;
|
||||
case '\u007F':
|
||||
case '\b':
|
||||
password = password.slice(0, -1);
|
||||
break;
|
||||
default:
|
||||
password += c;
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
stdin.on('data', onData);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 选择函数 - 显示选项列表并获取用户选择
|
||||
*
|
||||
@@ -162,20 +205,28 @@ function ask(question, defaultValue = '') {
|
||||
* ]);
|
||||
*/
|
||||
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');
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// 系统命令执行函数
|
||||
// =============================================================================
|
||||
@@ -752,7 +803,7 @@ async function configureDatabase() {
|
||||
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 ask('MySQL 密码', '');
|
||||
config.dbConfig.password = await askPassword('MySQL 密码');
|
||||
config.dbConfig.database = await ask('数据库名称', 'idc_management');
|
||||
}
|
||||
|
||||
@@ -1126,7 +1177,8 @@ async function autoInstallNginx() {
|
||||
function generateBackendEnv() {
|
||||
const envPath = path.join(__dirname, 'backend', '.env');
|
||||
|
||||
// 构建环境变量内容
|
||||
const jwtSecret = generateSecretKey(64);
|
||||
|
||||
let envContent = `# IDC设备管理系统 - 环境配置
|
||||
# 生成时间: ${new Date().toISOString()}
|
||||
|
||||
@@ -1140,6 +1192,13 @@ PORT=${config.backendPort}
|
||||
# 运行环境: development 或 production
|
||||
NODE_ENV=${config.nodeEnv}
|
||||
|
||||
# ==============================================
|
||||
# 安全配置
|
||||
# ==============================================
|
||||
|
||||
# JWT 密钥(自动生成,请妥善保管)
|
||||
JWT_SECRET=${jwtSecret}
|
||||
|
||||
# ==============================================
|
||||
# 数据库配置
|
||||
# ==============================================
|
||||
@@ -1148,7 +1207,6 @@ NODE_ENV=${config.nodeEnv}
|
||||
DB_TYPE=${config.dbType}
|
||||
`;
|
||||
|
||||
// 根据数据库类型添加相应配置
|
||||
if (config.dbType === 'sqlite') {
|
||||
envContent += `
|
||||
# SQLite 配置
|
||||
@@ -1165,9 +1223,9 @@ MYSQL_DATABASE=${config.dbConfig.database}
|
||||
`;
|
||||
}
|
||||
|
||||
// 写入文件
|
||||
fs.writeFileSync(envPath, envContent);
|
||||
log.success('后端环境变量文件已生成 (.env)');
|
||||
log.info(`JWT_SECRET 已自动生成 (${jwtSecret.length}位)`);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1489,7 +1547,10 @@ async function startServices() {
|
||||
|
||||
// 停止已有服务(避免冲突)
|
||||
log.info('停止已有服务...');
|
||||
runCommand('pm2 stop idc-backend idc-frontend 2>nul || true', { silent: true });
|
||||
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('启动后端服务...');
|
||||
|
||||
+9
-9
@@ -111,41 +111,41 @@ function isRootUser() {
|
||||
async function stopAndDeleteServices() {
|
||||
log.step('停止并删除服务');
|
||||
|
||||
// 检查 PM2 是否存在
|
||||
if (!commandExists('pm2')) {
|
||||
log.warning('未检测到 PM2,跳过服务停止步骤');
|
||||
return;
|
||||
}
|
||||
|
||||
// 停止并删除后端服务
|
||||
const isWindows = process.platform === 'win32';
|
||||
const nullRedirect = isWindows ? '2>nul' : '2>/dev/null';
|
||||
const orTrue = isWindows ? '|| exit 0' : '|| true';
|
||||
|
||||
log.info('停止后端服务 (idc-backend)...');
|
||||
const backendStop = runCommand('pm2 stop idc-backend 2>nul || true', { silent: true });
|
||||
const backendStop = runCommand(`pm2 stop idc-backend ${nullRedirect} ${orTrue}`, { silent: true });
|
||||
if (backendStop.success) {
|
||||
log.success('后端服务已停止');
|
||||
}
|
||||
|
||||
log.info('删除后端服务 (idc-backend)...');
|
||||
const backendDelete = runCommand('pm2 delete idc-backend 2>nul || true', { silent: true });
|
||||
const backendDelete = runCommand(`pm2 delete idc-backend ${nullRedirect} ${orTrue}`, { silent: true });
|
||||
if (backendDelete.success) {
|
||||
log.success('后端服务已删除');
|
||||
}
|
||||
|
||||
// 停止并删除前端服务
|
||||
log.info('停止前端服务 (idc-frontend)...');
|
||||
const frontendStop = runCommand('pm2 stop idc-frontend 2>nul || true', { silent: true });
|
||||
const frontendStop = runCommand(`pm2 stop idc-frontend ${nullRedirect} ${orTrue}`, { silent: true });
|
||||
if (frontendStop.success) {
|
||||
log.success('前端服务已停止');
|
||||
}
|
||||
|
||||
log.info('删除前端服务 (idc-frontend)...');
|
||||
const frontendDelete = runCommand('pm2 delete idc-frontend 2>nul || true', { silent: true });
|
||||
const frontendDelete = runCommand(`pm2 delete idc-frontend ${nullRedirect} ${orTrue}`, { silent: true });
|
||||
if (frontendDelete.success) {
|
||||
log.success('前端服务已删除');
|
||||
}
|
||||
|
||||
// 保存 PM2 配置
|
||||
log.info('保存 PM2 配置...');
|
||||
runCommand('pm2 save 2>nul || true', { silent: true });
|
||||
runCommand(`pm2 save ${nullRedirect} ${orTrue}`, { silent: true });
|
||||
|
||||
log.divider();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user