feat(install): 添加Linux安装引导脚本并优化安装/卸载流程

添加install.sh脚本支持Linux系统自动安装Node.js和依赖
优化install.js和uninstall.js脚本,增加非交互模式、健康检查、日志记录等功能
更新README.md和DEPLOYMENT.md文档,提供更详细的安装/卸载指南
在package.json中添加uninstall脚本命令
This commit is contained in:
zhang1106
2026-03-17 14:13:22 +08:00
parent 19bc1a32ef
commit 2905fb4fa3
6 changed files with 1159 additions and 202 deletions
+127 -18
View File
@@ -25,6 +25,8 @@
我们提供了交互式安装脚本,自动完成环境检测、依赖安装、数据库初始化和服务启动。
#### Linux 用户(支持自动安装 Node.js)
```bash
# 克隆项目(选择其一)
# Gitee(国内推荐)
@@ -33,36 +35,63 @@ git clone https://gitee.com/zhang96110/idc_assest.git
git clone https://github.com/gituib/idc_assest.git
cd idc_assest
#如果没安装nodejs需要先安装
# 方式1:使用 Shell 引导脚本(推荐,无需预装 Node.js
chmod +x install.sh && ./install.sh
**Ubuntu/Debian:**
```bash
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt-get install -y nodejs
# 方式2:一键安装(从远程)
curl -fsSL https://gitee.com/zhang96110/idc_assest/raw/main/install.sh | bash
```
**CentOS/RHEL/Fedora:**
```bash
curl -fsSL https://rpm.nodesource.com/setup_20.x | sudo bash -
sudo yum install -y nodejs
**Shell 脚本功能:**
- ✅ 自动检测 Linux 发行版(Ubuntu/Debian/CentOS/Arch
- ✅ 自动安装 Node.js 20.x(使用 NodeSource 或 nvm
- ✅ 自动安装系统依赖(git、curl 等)
- ✅ 调用 install.js 完成后续安装
系统已经安装了nodejs的可以直接执行一键安装脚本
#### Windows 用户
# 运行部署脚本
npm run deploy
# 或
```powershell
# 克隆项目
git clone https://gitee.com/zhang96110/idc_assest.git
cd idc_assest
# 运行安装脚本(需要先安装 Node.js)
node install.js
# 或使用 npm 命令
npm run deploy
```
#### 非交互式安装(快速部署)
```bash
# 使用默认配置快速安装
node install.js -y # 交互式安装
# 指定数据库类型
node install.js -y --db=mysql # 使用 MySQL
node install.js -y --db=sqlite # 使用 SQLite(默认)
# 指定端口
node install.js -y --port=3000 # 后端端口 3000
# 跳过前端构建
node install.js -y --skip-build
```
**脚本功能:**
- ✅ 自动检测 Node.js、npm、PM2、Nginx
- ✅ Linux 系统支持自动安装 Node.js(交互式
-**Linux 自动安装 Node.js**Ubuntu/Debian/CentOS/Arch
-**MySQL 连接测试**(配置后自动验证)
-**安装后健康检查**(验证服务是否正常)
- ✅ 交互式配置数据库(SQLite/MySQL
- ✅ 交互式选择运行环境(development/production
- ✅ 交互式选择前端部署方式(Nginx/PM2 serve
- ✅ 自动安装项目依赖
- ✅ 自动初始化数据库
- ✅ 自动构建前端项目
- ✅ 使用 PM2 启动和管理服务
- ✅ 日志持久化保存
---
@@ -775,13 +804,37 @@ time curl -o /dev/null -s -w "%{http_code}\n" http://localhost:8000/health
## 更新升级
### 方式一:一键更新脚本(推荐)
### 方式一:一键更新脚本(推荐)
```bash
npm run update
# 交互式更新
node update.js
# 查看帮助
node update.js --help
# 模拟运行(不执行实际操作)
node update.js --dry-run
# 跳过某些步骤
node update.js --skip-git # 跳过 Git 拉取
node update.js --skip-backup # 跳过数据库备份
node update.js --skip-migrate # 跳过数据库迁移
node update.js --skip-build # 跳过前端构建
node update.js --skip-deps # 跳过依赖安装
node update.js --skip-restart # 跳过服务重启
# 强制执行(忽略锁文件)
node update.js --force
```
功能:自动备份 → 拉取代码 → 更新依赖 → 重建前端 → 重启服务
**智能检测功能:**
-**依赖安装智能跳过** - 检测 package.json 变化,无变化则跳过
-**前端构建智能跳过** - 检测源码变化,无变化则跳过
-**数据库自动备份** - 更新前自动备份,支持回滚
-**健康检查验证** - 更新后验证服务是否正常
-**日志持久化** - 更新日志保存到 `logs/update_*.log`
-**锁机制** - 防止多个更新进程同时运行
### 方式二:手动更新
@@ -880,6 +933,61 @@ module.exports = {
---
## 卸载系统
### 方式一:一键卸载脚本(推荐)⭐
```bash
# 交互式卸载
node uninstall.js
# 查看帮助
node uninstall.js --help
# 强制卸载(无需确认)
node uninstall.js --force
# 卸载前自动备份
node uninstall.js --backup
# 跳过某些步骤
node uninstall.js --skip-db # 跳过数据库删除
node uninstall.js --skip-deps # 跳过依赖删除
```
**卸载功能:**
- ✅ 停止并删除 PM2 服务
- ✅ 清理 Nginx 配置
- ✅ 删除生成的配置文件
- ✅ 可选删除数据库文件
- ✅ 可选删除 node_modules
- ✅ 卸载前自动备份
- ✅ 日志持久化保存
### 方式二:手动卸载
```bash
# 1. 停止服务
pm2 stop idc-backend
pm2 delete idc-backend
pm2 save
# 2. 删除配置文件
rm backend/.env
rm deploy/ecosystem.config.js
rm deploy/nginx-idc.conf
# 3. 删除数据库(可选)
rm backend/idc_management.db
# 4. 删除依赖(可选)
rm -rf backend/node_modules
rm -rf frontend/node_modules
rm -rf frontend/dist
```
---
## 常见问题
### 端口冲突
@@ -996,7 +1104,8 @@ idc_assest/
├── docs/ # 项目文档
│ ├── api/ # 接口文档
│ └── images/ # 文档图片
├── install.js # 交互式安装脚本
├── install.sh # Linux 安装引导脚本(Shell
├── install.js # 交互式安装脚本(Node.js)⭐
├── update.js # 一键更新脚本 ⭐
├── uninstall.js # 卸载脚本
├── check.js # 环境检查脚本
+103 -8
View File
@@ -154,7 +154,9 @@
### 方式一:一键部署脚本(推荐)⭐
我们提供了交互式安装脚本,自动完成所有部署步骤
我们提供了交互式安装脚本,自动完成所有部署步骤
#### Linux 用户(支持自动安装 Node.js)
```bash
# 克隆项目(选择其一)
@@ -164,15 +166,49 @@ git clone https://gitee.com/zhang96110/idc_assest.git
git clone https://github.com/gituib/idc_assest.git
cd idc_assest
# 运行交互式安装脚本
npm run deploy
# 或
# 方式1:使用 Shell 引导脚本(推荐,无需预装 Node.js)
chmod +x install.sh && ./install.sh
# 方式2:一键安装(从远程)
curl -fsSL https://gitee.com/zhang96110/idc_assest/raw/main/install.sh | bash
```
#### Windows 用户
```powershell
# 克隆项目
git clone https://gitee.com/zhang96110/idc_assest.git
cd idc_assest
# 运行安装脚本(需要先安装 Node.js)
node install.js
# 或使用 npm 命令
npm run deploy
```
#### 非交互式安装(快速部署)
```bash
# 使用默认配置快速安装
node install.js -y # 交互式安装
# 指定数据库类型
node install.js -y --db=mysql # 使用 MySQL
node install.js -y --db=sqlite # 使用 SQLite(默认)
# 指定端口
node install.js -y --port=3000 # 后端端口 3000
# 跳过前端构建
node install.js -y --skip-build
```
**脚本功能:**
- ✅ 自动检测 Node.js、npm、PM2、Nginx
-**Linux 支持自动安装 Node.js**交互式
-**Linux 自动安装 Node.js**Ubuntu/Debian/CentOS/Arch
-**MySQL 连接测试**(配置后自动验证)
-**安装后健康检查**(验证服务是否正常)
- ✅ 交互式配置数据库(SQLite/MySQL
- ✅ 交互式选择运行环境(development/production
- ✅ 交互式选择前端部署方式(Nginx/PM2 serve
@@ -180,6 +216,7 @@ node install.js
- ✅ 自动初始化数据库
- ✅ 自动构建前端项目
- ✅ 使用 PM2 启动和管理服务
- ✅ 日志持久化保存
### 方式二:手动安装
@@ -585,7 +622,8 @@ jigui/
│ └── USER_GUIDE.md # 用户指南
├── scripts/ # 项目脚本
│ └── frontend-manager.js # 前端管理脚本
├── install.js # 交互式安装脚本
├── install.sh # Linux 安装引导脚本(Shell
├── install.js # 交互式安装脚本(Node.js)⭐
├── update.js # 一键更新脚本 ⭐
├── uninstall.js # 卸载脚本
├── package.json # 项目根依赖
@@ -605,10 +643,34 @@ jigui/
### 一键更新(推荐)⭐
```bash
npm run update
# 交互式更新
node update.js
# 查看帮助
node update.js --help
# 模拟运行(不执行实际操作)
node update.js --dry-run
# 跳过某些步骤
node update.js --skip-git # 跳过 Git 拉取
node update.js --skip-backup # 跳过数据库备份
node update.js --skip-migrate # 跳过数据库迁移
node update.js --skip-build # 跳过前端构建
node update.js --skip-deps # 跳过依赖安装
node update.js --skip-restart # 跳过服务重启
# 强制执行(忽略锁文件)
node update.js --force
```
功能:自动备份 → 拉取代码 → 更新依赖 → 重建前端 → 重启服务
**智能检测功能:**
-**依赖安装智能跳过** - 检测 package.json 变化,无变化则跳过
-**前端构建智能跳过** - 检测源码变化,无变化则跳过
-**数据库自动备份** - 更新前自动备份,支持回滚
-**健康检查验证** - 更新后验证服务是否正常
-**日志持久化** - 更新日志保存到 `logs/update_*.log`
-**锁机制** - 防止多个更新进程同时运行
### 手动更新
@@ -628,6 +690,39 @@ pm2 restart idc-backend
---
## 卸载系统
### 一键卸载(推荐)⭐
```bash
# 交互式卸载
node uninstall.js
# 查看帮助
node uninstall.js --help
# 强制卸载(无需确认)
node uninstall.js --force
# 卸载前自动备份
node uninstall.js --backup
# 跳过某些步骤
node uninstall.js --skip-db # 跳过数据库删除
node uninstall.js --skip-deps # 跳过依赖删除
```
**卸载功能:**
- ✅ 停止并删除 PM2 服务
- ✅ 清理 Nginx 配置
- ✅ 删除生成的配置文件
- ✅ 可选删除数据库文件
- ✅ 可选删除 node_modules
- ✅ 卸载前自动备份
- ✅ 日志持久化保存
---
## API接口
完整接口文档请参考 [docs/api/README.md](docs/api/README.md)。
+315 -123
View File
@@ -15,12 +15,12 @@
* - 使用 PM2 启动和管理后端服务
*
* 使用方法:
* node install.js
*
* npm run deploy
* node install.js # 交互式安装
* node install.js --help # 查看帮助
* node install.js --non-interactive # 非交互式(使用默认配置)
*
* 系统要求:
* - Node.js >= 14.0.0
* - Node.js >= 16.0.0
* - npm >= 6.0.0
* - Windows/Linux/macOS
*
@@ -29,54 +29,99 @@
* ============================================================================
*/
// Node.js 内置模块引入
const readline = require('readline'); // 读取命令行输入
const { execSync } = require('child_process'); // 执行系统命令
const fs = require('fs'); // 文件系统操作
const path = require('path'); // 路径处理
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');
/**
* ANSI 颜色代码配置
* 用于在终端输出彩色文字,提升可读性
*/
const colors = {
reset: '\x1b[0m', // 重置颜色
bright: '\x1b[1m', // 高亮
green: '\x1b[32m', // 绿色(成功)
yellow: '\x1b[33m', // 黄色(警告)
red: '\x1b[31m', // 红色(错误)
cyan: '\x1b[36m', // 青色(信息)
gray: '\x1b[90m' // 灰色(分隔线)
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}`)
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();
}
}
// =============================================================================
// 配置存储对象
// =============================================================================
@@ -286,12 +331,12 @@ function commandExists(command) {
/**
* 检查 Node.js 版本是否满足要求
*
* @returns {boolean} 版本是否 >= 14.0.0
* @returns {boolean} 版本是否 >= MIN_NODE_VERSION
*/
function checkNodeVersion() {
const version = process.version; // 获取当前 Node 版本,如 "v20.10.0"
const major = parseInt(version.slice(1).split('.')[0]); // 提取主版本号
return major >= 14;
const version = process.version;
const major = parseInt(version.slice(1).split('.')[0]);
return major >= MIN_NODE_VERSION;
}
/**
@@ -785,31 +830,78 @@ async function checkEnvironment() {
* 1. 选择数据库类型(SQLite / MySQL
* 2. 如选择 MySQL,提示输入连接参数
*/
async function configureDatabase() {
async function configureDatabase(cmdArgs) {
log.step('数据库配置');
// 选择数据库类型
config.dbType = await select('选择数据库类型:', [
{ label: 'SQLite(零配置,适合开发/小规模)', value: 'sqlite' },
{ label: 'MySQL(生产环境推荐)', value: 'mysql' }
]);
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' }
]);
}
// MySQL 配置
if (config.dbType === 'mysql') {
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');
// 依次询问 MySQL 连接参数
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');
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 {
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');
}
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 testMySQLConnection() {
log.subStep('测试 MySQL 连接...');
try {
const mysql = require('mysql2/promise');
const connection = await mysql.createConnection({
host: config.dbConfig.host,
port: parseInt(config.dbConfig.port),
user: config.dbConfig.username,
password: config.dbConfig.password,
database: config.dbConfig.database,
connectTimeout: 5000
});
await connection.execute('SELECT 1');
await connection.end();
log.success('MySQL 连接测试成功');
return { success: true };
} catch (error) {
return { success: false, error: error.message };
}
}
/**
* 服务配置函数
*
@@ -820,25 +912,40 @@ async function configureDatabase() {
* 4. 如选择 Nginx 但未安装,提供安装选项
* 5. 配置前端端口和域名
*/
async function configureServices() {
async function configureServices(cmdArgs) {
log.step('服务配置');
// 1. 后端端口配置
config.backendPort = await ask('后端服务端口', '8000');
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');
}
// 2. 运行环境选择
config.nodeEnv = await select('选择运行环境:', [
{ label: 'production(生产模式,性能优化,推荐正式部署)', value: 'production' },
{ label: 'development(开发模式,详细日志,便于调试)', value: 'development' }
]);
if (cmdArgs?.nonInteractive) {
config.nodeEnv = 'production';
log.info('使用默认配置: 运行环境 = production');
} else {
config.nodeEnv = await select('选择运行环境:', [
{ label: 'production(生产模式,性能优化,推荐正式部署)', value: 'production' },
{ label: 'development(开发模式,详细日志,便于调试)', value: 'development' }
]);
}
// 3. 前端部署方式选择
config.frontendDeploy = await select('前端部署方式:', [
{ label: 'Nginx(性能最优,推荐生产环境)', value: 'nginx' },
{ label: 'PM2 serve(简单快捷,无需额外安装)', value: 'pm2' }
]);
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' }
]);
}
// 3. 如果选择 Nginx,检查是否已安装
if (config.frontendDeploy === 'nginx') {
const nginxInstalled = isNginxInstalled();
@@ -846,55 +953,54 @@ async function configureServices() {
log.warning('未检测到 Nginx 安装');
if (process.platform === 'win32') {
// Windows 下询问是否自动安装
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}`);
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') {
// Linux 下询问是否自动安装
const autoInstall = await ask('是否自动安装 Nginx? (Y/n)', 'Y');
if (autoInstall.toLowerCase() === 'y') {
const installed = await autoInstallNginxLinux();
if (!installed) {
log.error('Nginx 自动安装失败');
log.info('请手动安装后重新运行脚本');
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();
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);
const continueDeploy = await ask('是否继续部署(后端将启动,前端需手动配置 Nginx? (Y/n)', 'Y');
if (continueDeploy.toLowerCase() !== 'y') {
log.info('已取消部署,请安装 Nginx 后重新运行');
process.exit(0);
}
}
}
} else {
// macOS 下提供安装命令
log.info('请使用 Homebrew 安装 Nginx');
console.log(` ${colors.cyan}brew install nginx${colors.reset}`);
console.log(`\n安装完成后,请重新运行此脚本继续部署。\n`);
const continueDeploy = await ask('是否继续部署(后端将启动,前端需手动配置 Nginx? (Y/n)', 'Y');
if (continueDeploy.toLowerCase() !== 'y') {
log.info('已取消部署,请安装 Nginx 后重新运行');
process.exit(0);
}
}
}
// Nginx 端口和域名配置
config.frontendPort = await ask('Nginx 监听端口', '80');
config.domain = await ask('域名(没有则填localhost', 'localhost');
if (!cmdArgs?.nonInteractive) {
config.frontendPort = await ask('Nginx 监听端口', '80');
config.domain = await ask('域名(没有则填localhost', 'localhost');
} else {
config.frontendPort = '80';
config.domain = 'localhost';
}
} else {
// PM2 serve 端口配置
config.frontendPort = await ask('前端服务端口', '3000');
if (!cmdArgs?.nonInteractive) {
config.frontendPort = await ask('前端服务端口', '3000');
}
}
log.divider();
@@ -1658,47 +1764,133 @@ function printManagementCommands() {
* 10. 输出管理命令
*/
async function main() {
// 显示欢迎信息
installStartTime = Date.now();
const cmdArgs = parseArgs();
if (cmdArgs.help) {
showHelp();
return;
}
initLogFile();
console.log(`
${colors.bright}${colors.cyan}
╔══════════════════════════════════════════════════════════╗
║ IDC设备管理系统 - 交互式安装部署脚本
║ IDC设备管理系统 - 安装部署脚本 v${SCRIPT_VERSION}
║ Interactive Installation & Deployment Script ║
╚══════════════════════════════════════════════════════════╝
${colors.reset}`);
if (cmdArgs.nonInteractive) {
log.info('运行模式: 非交互式(使用默认配置)');
}
try {
// 步骤1:环境检测
await checkEnvironment();
await configureDatabase(cmdArgs);
await configureServices(cmdArgs);
if (!cmdArgs.nonInteractive) {
await confirmConfiguration();
}
// 步骤2:交互式配置
await configureDatabase();
await configureServices();
await confirmConfiguration();
// 步骤3:生成配置文件
generateBackendEnv();
generatePM2Config();
generateNginxConfig();
// 步骤4:安装和部署
await installDependencies();
await initDatabase();
await buildFrontend();
if (!cmdArgs.skipBuild) {
await buildFrontend();
} else {
log.info('已跳过前端构建');
}
await startServices();
// 步骤5:输出管理命令
printManagementCommands();
await healthCheck();
printSummary();
} catch (error) {
log.error(`部署失败: ${error.message}`);
console.error(error);
process.exit(1);
} finally {
// 关闭 readline 接口
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();
+420
View File
@@ -0,0 +1,420 @@
#!/bin/bash
# ============================================================================
# IDC设备管理系统 - Linux 安装引导脚本
# ============================================================================
#
# 功能:检测并安装 Node.js,然后运行 install.js
#
# 使用方法:
# 方式1 - 直接运行:
# chmod +x install.sh && ./install.sh
#
# 方式2 - 一键安装(推荐):
# curl -fsSL https://your-domain/install.sh | bash
#
# 方式3 - 从 GitHub
# curl -fsSL https://raw.githubusercontent.com/xxx/xxx/main/install.sh | bash
#
# ============================================================================
set -e
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
CYAN='\033[0;36m'
BRIGHT='\033[1m'
RESET='\033[0m'
# 日志函数
log_info() { echo -e "${CYAN}${RESET} $1"; }
log_success() { echo -e "${GREEN}${RESET} $1"; }
log_warning() { echo -e "${YELLOW}${RESET} $1"; }
log_error() { echo -e "${RED}${RESET} $1"; }
log_step() { echo -e "\n${BRIGHT}${CYAN}$1${RESET}"; }
log_divider() { echo -e "${YELLOW}$(printf '─%.0s' {1..60})${RESET}"; }
# 脚本版本
SCRIPT_VERSION="1.0.0"
MIN_NODE_VERSION=16
# 获取脚本所在目录
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# 显示欢迎信息
show_banner() {
echo -e "
${BRIGHT}${CYAN}
╔══════════════════════════════════════════════════════════╗
║ IDC设备管理系统 - Linux 安装引导脚本 v${SCRIPT_VERSION}
║ Linux Bootstrap Installation Script ║
╚══════════════════════════════════════════════════════════╝
${RESET}"
}
# 检测 Linux 发行版
detect_distro() {
if [ -f /etc/os-release ]; then
. /etc/os-release
echo "$ID"
elif [ -f /etc/redhat-release ]; then
echo "rhel"
else
echo "unknown"
fi
}
# 检查是否为 root 用户
is_root() {
[ "$(id -u)" -eq 0 ]
}
# 获取 sudo 前缀
get_sudo() {
if is_root; then
echo ""
else
echo "sudo"
fi
}
# 检查命令是否存在
command_exists() {
command -v "$1" >/dev/null 2>&1
}
# 获取 Node.js 版本号
get_node_version() {
if command_exists node; then
node -e "console.log(process.versions.node.split('.')[0])"
else
echo "0"
fi
}
# 检查 Node.js 版本
check_node_version() {
local version=$(get_node_version)
[ "$version" -ge "$MIN_NODE_VERSION" ]
}
# 安装 Node.js - Ubuntu/Debian
install_node_debian() {
local sudo=$(get_sudo)
log_info "使用 NodeSource 安装 Node.js 20.x..."
# 安装 curl(如果没有)
if ! command_exists curl; then
log_info "安装 curl..."
$sudo apt-get update -qq
$sudo apt-get install -y -qq curl
fi
# 添加 NodeSource 源
log_info "添加 NodeSource 源..."
curl -fsSL https://deb.nodesource.com/setup_20.x | $sudo -E bash -
# 安装 Node.js
log_info "安装 Node.js..."
$sudo apt-get install -y -qq nodejs
# 验证安装
if command_exists node; then
log_success "Node.js $(node -v) 安装成功"
return 0
else
return 1
fi
}
# 安装 Node.js - CentOS/RHEL/Fedora
install_node_rhel() {
local sudo=$(get_sudo)
log_info "使用 NodeSource 安装 Node.js 20.x..."
# 安装 curl(如果没有)
if ! command_exists curl; then
log_info "安装 curl..."
$sudo yum install -y -q curl
fi
# 添加 NodeSource 源
log_info "添加 NodeSource 源..."
curl -fsSL https://rpm.nodesource.com/setup_20.x | $sudo bash -
# 安装 Node.js
log_info "安装 Node.js..."
$sudo yum install -y -q nodejs
# 验证安装
if command_exists node; then
log_success "Node.js $(node -v) 安装成功"
return 0
else
return 1
fi
}
# 安装 Node.js - Arch Linux
install_node_arch() {
local sudo=$(get_sudo)
log_info "使用 pacman 安装 Node.js..."
$sudo pacman -S --noconfirm nodejs npm
if command_exists node; then
log_success "Node.js $(node -v) 安装成功"
return 0
else
return 1
fi
}
# 安装 Node.js - 使用 nvm
install_node_nvm() {
log_info "使用 nvm 安装 Node.js..."
# 安装 nvm
if [ ! -d "$HOME/.nvm" ]; then
log_info "安装 nvm..."
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
fi
# 加载 nvm
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
# 安装 Node.js 20
log_info "安装 Node.js 20..."
nvm install 20
nvm use 20
nvm alias default 20
if command_exists node; then
log_success "Node.js $(node -v) 安装成功"
return 0
else
return 1
fi
}
# 自动安装 Node.js
auto_install_node() {
local distro=$(detect_distro)
log_step "自动安装 Node.js"
log_info "检测到 Linux 发行版: $distro"
case $distro in
ubuntu|debian)
install_node_debian
;;
centos|rhel|fedora|rocky|almalinux)
install_node_rhel
;;
arch|manjaro)
install_node_arch
;;
*)
log_warning "未知发行版,尝试使用 nvm 安装..."
install_node_nvm
;;
esac
}
# 显示手动安装指引
show_manual_install_guide() {
echo -e "
${BRIGHT}Node.js 手动安装指引:${RESET}
${YELLOW}Ubuntu/Debian:${RESET}
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt-get install -y nodejs
${YELLOW}CentOS/RHEL/Fedora:${RESET}
curl -fsSL https://rpm.nodesource.com/setup_20.x | sudo bash -
sudo yum install -y nodejs
${YELLOW}Arch Linux:${RESET}
sudo pacman -S nodejs npm
${YELLOW}使用 nvm(推荐开发者):${RESET}
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
source ~/.bashrc
nvm install 20
nvm use 20
${YELLOW}验证安装:${RESET}
node -v # 应显示 v20.x.x
npm -v # 应显示 10.x.x
${CYAN}安装完成后,请重新运行此脚本继续安装 IDC设备管理系统${RESET}
"
}
# 检查并安装依赖
check_and_install_dependencies() {
log_step "检查系统依赖"
local sudo=$(get_sudo)
local distro=$(detect_distro)
local needs_install=()
# 检查 git
if ! command_exists git; then
needs_install+=("git")
fi
# 检查 curl
if ! command_exists curl; then
needs_install+=("curl")
fi
# 检查 wget(可选)
if ! command_exists wget; then
needs_install+=("wget")
fi
if [ ${#needs_install[@]} -gt 0 ]; then
log_info "需要安装依赖: ${needs_install[*]}"
case $distro in
ubuntu|debian)
$sudo apt-get update -qq
$sudo apt-get install -y -qq "${needs_install[@]}"
;;
centos|rhel|fedora|rocky|almalinux)
$sudo yum install -y -q "${needs_install[@]}"
;;
arch|manjaro)
$sudo pacman -S --noconfirm "${needs_install[@]}"
;;
*)
log_warning "无法自动安装依赖,请手动安装: ${needs_install[*]}"
return 1
;;
esac
log_success "依赖安装完成"
else
log_success "系统依赖已满足"
fi
}
# 主函数
main() {
show_banner
# 检查是否在正确的目录
if [ ! -f "$SCRIPT_DIR/install.js" ]; then
log_error "未找到 install.js 文件"
log_info "请在项目根目录运行此脚本"
exit 1
fi
# 检查 Node.js
log_step "检查 Node.js 环境"
if command_exists node; then
local node_version=$(get_node_version)
log_info "检测到 Node.js: $(node -v)"
if check_node_version; then
log_success "Node.js 版本满足要求 (>= v$MIN_NODE_VERSION)"
else
log_warning "Node.js 版本过低 (v$node_version < v$MIN_NODE_VERSION)"
echo -e "\n${YELLOW}请选择操作:${RESET}"
echo " 1) 自动升级 Node.js 到 v20(推荐)"
echo " 2) 显示手动安装指引"
echo " 3) 退出"
read -p "请选择 [1-3]: " choice
case $choice in
1)
auto_install_node || {
log_error "Node.js 升级失败"
show_manual_install_guide
exit 1
}
# 重新加载环境
export PATH="/usr/bin:$HOME/.nvm/versions/node/v20*/bin:$PATH"
hash -r
;;
2)
show_manual_install_guide
exit 0
;;
3)
log_info "已取消安装"
exit 0
;;
*)
log_error "无效选择"
exit 1
;;
esac
fi
else
log_warning "未检测到 Node.js"
echo -e "\n${YELLOW}请选择操作:${RESET}"
echo " 1) 自动安装 Node.js v20(推荐)"
echo " 2) 显示手动安装指引"
echo " 3) 退出"
read -p "请选择 [1-3]: " choice
case $choice in
1)
check_and_install_dependencies
auto_install_node || {
log_error "Node.js 安装失败"
show_manual_install_guide
exit 1
}
# 重新加载环境
export PATH="/usr/bin:$HOME/.nvm/versions/node/v20*/bin:$PATH"
hash -r
;;
2)
show_manual_install_guide
exit 0
;;
3)
log_info "已取消安装"
exit 0
;;
*)
log_error "无效选择"
exit 1
;;
esac
fi
# 检查 npm
log_step "检查 npm"
if command_exists npm; then
log_success "npm $(npm -v)"
else
log_error "npm 未安装"
exit 1
fi
log_divider
log_success "环境检查通过!"
# 运行 install.js
log_step "启动安装程序"
log_info "正在运行 install.js..."
echo ""
cd "$SCRIPT_DIR"
node install.js "$@"
}
# 运行主函数
main "$@"
+2 -1
View File
@@ -8,7 +8,8 @@
"start:frontend": "cd frontend && npm run dev",
"install:all": "npm install && cd backend && npm install && cd ../frontend && npm install",
"deploy": "node install.js",
"update": "node update.js"
"update": "node update.js",
"uninstall": "node uninstall.js"
},
"keywords": [
"idc",
+192 -52
View File
@@ -2,7 +2,7 @@
/**
* ============================================================================
* IDC设备管理系统 - 卸载脚本
* IDC设备管理系统 - 卸载脚本 v2.0.0
* ============================================================================
*
* 功能说明:
@@ -13,24 +13,20 @@
* - 可选删除 node_modules 和构建产物
*
* 使用方法:
* node uninstall.js
*
* 注意事项:
* - 卸载前请确保已备份重要数据
* - 此脚本不会删除项目源代码
* - MySQL 数据库不会被删除,需手动清理
* ============================================================================
* node uninstall.js # 交互式卸载
* node uninstall.js --help # 查看帮助
* node uninstall.js --force # 强制卸载(无需确认)
* node uninstall.js --backup # 卸载前自动备份
*/
// Node.js 内置模块引入
const readline = require('readline');
const { execSync } = require('child_process');
const fs = require('fs');
const path = require('path');
// =============================================================================
// 工具函数:颜色输出和日志
// =============================================================================
const SCRIPT_VERSION = '2.0.0';
const BACKUP_DIR = path.join(__dirname, 'backup');
const LOG_DIR = path.join(__dirname, 'logs');
const colors = {
reset: '\x1b[0m',
@@ -39,7 +35,8 @@ const colors = {
yellow: '\x1b[33m',
red: '\x1b[31m',
cyan: '\x1b[36m',
gray: '\x1b[90m'
gray: '\x1b[90m',
magenta: '\x1b[35m'
};
const log = {
@@ -48,9 +45,72 @@ const log = {
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}`)
divider: () => console.log(`${colors.gray}${'─'.repeat(60)}${colors.reset}`),
subStep: (msg) => console.log(` ${colors.gray}${colors.reset} ${msg}`)
};
let logFileStream = null;
let uninstallStartTime = null;
let deletedItems = [];
let backedUpItems = [];
function parseArgs() {
const args = process.argv.slice(2);
return {
force: args.includes('--force') || args.includes('-f'),
backup: args.includes('--backup') || args.includes('-b'),
skipDb: args.includes('--skip-db'),
skipDeps: args.includes('--skip-deps'),
help: args.includes('--help') || args.includes('-h')
};
}
function showHelp() {
console.log(`
${colors.bright}IDC设备管理系统 - 卸载脚本 v${SCRIPT_VERSION}${colors.reset}
用法: node uninstall.js [选项]
选项:
-f, --force 强制卸载(无需确认)
-b, --backup 卸载前自动备份数据库
--skip-db 跳过数据库删除
--skip-deps 跳过依赖删除
-h, --help 显示帮助信息
示例:
node uninstall.js # 交互式卸载
node uninstall.js --force # 强制卸载
node uninstall.js --backup # 卸载前备份
`);
process.exit(0);
}
function initLogFile() {
if (!fs.existsSync(LOG_DIR)) {
fs.mkdirSync(LOG_DIR, { recursive: true });
}
const logFileName = `uninstall_${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();
}
}
// =============================================================================
// 交互式输入函数
// =============================================================================
@@ -257,9 +317,9 @@ async function cleanupConfigFiles() {
log.step('清理生成的配置文件');
const filesToDelete = [
{ path: path.join(__dirname, 'backend', '.env'), name: '后端环境变量 (.env)' },
{ path: path.join(__dirname, 'deploy', 'ecosystem.config.js'), name: 'PM2 配置 (ecosystem.config.js)' },
{ path: path.join(__dirname, 'deploy', 'nginx-idc.conf'), name: 'Nginx 配置 (nginx-idc.conf)' }
{ path: path.join(__dirname, 'backend', '.env'), name: '后端环境变量 (.env)', type: '配置文件' },
{ path: path.join(__dirname, 'deploy', 'ecosystem.config.js'), name: 'PM2 配置 (ecosystem.config.js)', type: '配置文件' },
{ path: path.join(__dirname, 'deploy', 'nginx-idc.conf'), name: 'Nginx 配置 (nginx-idc.conf)', type: '配置文件' }
];
for (const file of filesToDelete) {
@@ -267,6 +327,7 @@ async function cleanupConfigFiles() {
try {
fs.unlinkSync(file.path);
log.success(`${file.name} 已删除`);
deletedItems.push({ type: file.type, name: file.name });
} catch (error) {
log.error(`删除 ${file.name} 失败: ${error.message}`);
}
@@ -275,7 +336,6 @@ async function cleanupConfigFiles() {
}
}
// 检查 deploy 目录是否为空,如果是则删除
const deployDir = path.join(__dirname, 'deploy');
if (fs.existsSync(deployDir)) {
try {
@@ -283,6 +343,7 @@ async function cleanupConfigFiles() {
if (files.length === 0) {
fs.rmdirSync(deployDir);
log.success('deploy 目录已删除');
deletedItems.push({ type: '目录', name: 'deploy/' });
} else {
log.info('deploy 目录不为空,保留');
}
@@ -350,10 +411,10 @@ async function cleanupDatabase() {
const confirm = await ask('\n是否删除 SQLite 数据库文件? (y/N)', 'N');
if (confirm.toLowerCase() === 'y') {
try {
// 尝试停止服务后再删除(避免文件被占用)
log.info('正在删除数据库文件...');
fs.unlinkSync(sqliteDbPath);
log.success('SQLite 数据库已删除');
deletedItems.push({ type: '数据库', name: `SQLite (${dbSize} MB)` });
} catch (error) {
log.error(`删除失败: ${error.message}`);
if (error.message.includes('EBUSY') || error.message.includes('resource busy')) {
@@ -399,11 +460,11 @@ async function cleanupDatabase() {
// 依赖和构建产物清理
// =============================================================================
async function cleanupDependencies() {
async function cleanupDependencies(cmdArgs) {
log.step('依赖和构建产物清理');
log.warning('此操作将删除 node_modules 和构建产物,需要重新安装依赖才能再次运行');
const confirm = await ask('是否删除依赖和构建产物? (y/N)', 'N');
const confirm = cmdArgs?.force ? 'y' : await ask('是否删除依赖和构建产物? (y/N)', 'N');
if (confirm.toLowerCase() !== 'y') {
log.info('跳过依赖清理');
@@ -411,9 +472,9 @@ async function cleanupDependencies() {
}
const dirsToDelete = [
{ path: path.join(__dirname, 'backend', 'node_modules'), name: '后端 node_modules' },
{ path: path.join(__dirname, 'frontend', 'node_modules'), name: '前端 node_modules' },
{ path: path.join(__dirname, 'frontend', 'dist'), name: '前端构建产物 (dist)' }
{ path: path.join(__dirname, 'backend', 'node_modules'), name: '后端 node_modules', type: '依赖' },
{ path: path.join(__dirname, 'frontend', 'node_modules'), name: '前端 node_modules', type: '依赖' },
{ path: path.join(__dirname, 'frontend', 'dist'), name: '前端构建产物 (dist)', type: '构建产物' }
];
for (const dir of dirsToDelete) {
@@ -421,6 +482,7 @@ async function cleanupDependencies() {
try {
fs.rmSync(dir.path, { recursive: true, force: true });
log.success(`${dir.name} 已删除`);
deletedItems.push({ type: dir.type, name: dir.name });
} catch (error) {
log.error(`删除 ${dir.name} 失败: ${error.message}`);
}
@@ -463,52 +525,130 @@ async function cleanupLogs() {
// 主函数
// =============================================================================
async function backupDatabase() {
log.step('备份数据库');
const sqliteDbPath = path.join(__dirname, 'backend', 'idc_management.db');
if (!fs.existsSync(sqliteDbPath)) {
log.info('未找到 SQLite 数据库文件,跳过备份');
return false;
}
if (!fs.existsSync(BACKUP_DIR)) {
fs.mkdirSync(BACKUP_DIR, { recursive: true });
}
const backupPath = path.join(BACKUP_DIR, `database_backup_${Date.now()}.db`);
try {
fs.copyFileSync(sqliteDbPath, backupPath);
backedUpItems.push({ type: '数据库', path: backupPath });
log.success(`数据库已备份: ${path.basename(backupPath)}`);
return true;
} catch (error) {
log.error(`备份失败: ${error.message}`);
return false;
}
}
function printSummary() {
const duration = ((Date.now() - uninstallStartTime) / 1000).toFixed(1);
console.log(`
${colors.bright}${colors.magenta}
╔══════════════════════════════════════════════════════════╗
║ 卸载摘要 ║
╚══════════════════════════════════════════════════════════╝${colors.reset}`);
console.log(`\n ${colors.cyan}卸载耗时:${colors.reset} ${duration}`);
if (deletedItems.length > 0) {
console.log(`\n ${colors.cyan}已删除项目:${colors.reset}`);
deletedItems.forEach(item => {
console.log(` - ${item.type}: ${item.name}`);
});
}
if (backedUpItems.length > 0) {
console.log(`\n ${colors.cyan}已备份项目:${colors.reset}`);
backedUpItems.forEach(item => {
console.log(` - ${item.type}: ${path.basename(item.path)}`);
});
console.log(`\n ${colors.yellow}备份位置:${colors.reset} ${BACKUP_DIR}`);
}
console.log(`\n ${colors.cyan}保留的文件:${colors.reset}`);
console.log(` - 项目源代码 (backend/, frontend/)`);
console.log(` - 上传的文件 (backend/uploads/)`);
console.log(`\n ${colors.cyan}重新部署:${colors.reset}`);
console.log(` node install.js`);
log.divider();
log.success('卸载完成!');
}
async function main() {
uninstallStartTime = Date.now();
const cmdArgs = parseArgs();
if (cmdArgs.help) {
showHelp();
return;
}
initLogFile();
console.log(`
${colors.bright}${colors.yellow}
╔══════════════════════════════════════════════════════════╗
║ IDC设备管理系统 - 卸载脚本
║ IDC设备管理系统 - 卸载脚本 v${SCRIPT_VERSION}
║ Uninstallation Script ║
╚══════════════════════════════════════════════════════════╝
${colors.reset}`);
log.warning('此脚本将卸载 IDC设备管理系统');
log.warning('请确保已备份重要数据!');
if (cmdArgs.force) {
log.info('运行模式: 强制卸载(无需确认)');
}
log.divider();
const confirm = await ask('确认要开始卸载? (y/N)', 'N');
if (confirm.toLowerCase() !== 'y') {
log.info('已取消卸载');
rl.close();
return;
if (!cmdArgs.force) {
const confirm = await ask('确认要开始卸载? (y/N)', 'N');
if (confirm.toLowerCase() !== 'y') {
log.info('已取消卸载');
rl.close();
closeLogFile();
return;
}
}
try {
// 步骤1:停止并删除服务
if (cmdArgs.backup) {
await backupDatabase();
}
await stopAndDeleteServices();
// 步骤2:清理 Nginx 配置
await cleanupNginxConfig();
// 步骤3:清理配置文件
await cleanupConfigFiles();
// 步骤4:数据库清理
await cleanupDatabase();
// 步骤5:日志清理
if (!cmdArgs.skipDb) {
await cleanupDatabase();
} else {
log.info('已跳过数据库删除');
}
await cleanupLogs();
if (!cmdArgs.skipDeps) {
await cleanupDependencies();
} else {
log.info('已跳过依赖删除');
}
// 步骤6:依赖和构建产物清理(可选)
await cleanupDependencies();
// 完成
log.divider();
log.success('卸载完成!');
console.log(`\n${colors.bright}系统已完全卸载,以下文件保留:${colors.reset}`);
console.log(` - 项目源代码(backend/、frontend/`);
console.log(` - 上传的文件(如有)`);
console.log(`\n如需重新部署,请运行: ${colors.cyan}node install.js${colors.reset}`);
printSummary();
} catch (error) {
log.error(`卸载失败: ${error.message}`);
@@ -516,8 +656,8 @@ ${colors.reset}`);
process.exit(1);
} finally {
rl.close();
closeLogFile();
}
}
// 运行主函数
main();