feat(install): 添加Linux安装引导脚本并优化安装/卸载流程
添加install.sh脚本支持Linux系统自动安装Node.js和依赖 优化install.js和uninstall.js脚本,增加非交互模式、健康检查、日志记录等功能 更新README.md和DEPLOYMENT.md文档,提供更详细的安装/卸载指南 在package.json中添加uninstall脚本命令
This commit is contained in:
+127
-18
@@ -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 # 环境检查脚本
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user