diff --git a/README.md b/README.md index 5345b22..8c1f65c 100644 --- a/README.md +++ b/README.md @@ -1,29 +1,31 @@ -# jsite 自动化部署和管理脚本 +# jsite.sh - 前端自动化部署脚本 -这是一个集成了完整部署和日常管理功能的自动化脚本,支持多种任务模式。 +## 概述 -## 功能特性 +`jsite.sh` 是一个用于自动化部署和管理前端网站的bash脚本。它支持多种操作模式,包括部署、启动、停止、重启等。 -- 🚀 **完整部署**:一键部署完整的jsite环境 -- 🏗️ **网站管理**:创建、删除、构建、启动、停止、重启网站 -- 📊 **状态监控**:查看网站运行状态和日志 -- 🔧 **环境管理**:Docker、Node.js、PM2、Traefik等环境组件 -- ⚡ **智能端口分配**:自动分配和管理端口资源 +## 主要功能 + +- 自动创建和管理jingrow用户 +- 安装和配置Node.js、npm、PM2 +- 自动化部署前端项目 +- 集成Traefik反向代理 +- 支持多种操作模式 ## 使用方法 ### 基本语法 ```bash -./install_jsite.sh --mode <任务模式> [其他参数] +./jsite.sh --mode <模式> --site-name <网站名称> ``` -### 支持的任务模式 +### 支持的模式 -| 模式 | 功能 | 示例 | +| 模式 | 描述 | 示例 | |------|------|------| -| `deploy` | 完整部署模式(默认) | `--mode deploy --site-name myproject` | -| `create` | 创建新网站 | `--mode create --site-name myproject --git-repo <仓库地址>` | +| `deploy` | 完整部署网站 | `--mode deploy --site-name myproject` | +| `create` | 创建新网站 | `--mode create --site-name myproject` | | `delete` | 删除网站 | `--mode delete --site-name myproject` | | `build` | 构建网站 | `--mode build --site-name myproject` | | `start` | 启动网站 | `--mode start --site-name myproject` | @@ -33,157 +35,152 @@ | `list` | 列出所有网站 | `--mode list` | | `logs` | 查看网站日志 | `--mode logs --site-name myproject` | -### 常用参数 - -| 参数 | 说明 | 默认值 | -|------|------|--------| -| `--mode` | 任务模式 | `deploy` | -| `--site-name` | 网站名称 | `jingrow` | -| `--git-repo` | Git仓库地址 | `http://git.jingrow.com:3000/jsite/jingrow` | -| `--node-version` | Node.js版本 | `22` | -| `--start-port` | 起始端口 | `3001` | -| `--site-url` | 网站域名 | `starrbud.com` | - -### 使用示例 - -#### 1. 完整部署新项目 +### 常用命令示例 ```bash -./install_jsite.sh \ - --mode deploy \ - --site-name myproject \ - --git-repo http://git.example.com/myproject \ - --site-url myproject.com \ - --public-ip 8.217.167.199 -``` +# 部署新网站 +./jsite.sh --mode deploy --site-name jingrow -#### 2. 创建新网站 - -```bash -./install_jsite.sh \ - --mode create \ - --site-name myproject \ - --git-repo http://git.example.com/myproject \ - --site-url myproject.com -``` - -#### 3. 构建和启动网站 - -```bash -# 构建项目 -./install_jsite.sh --mode build --site-name myproject - -# 启动项目 -./install_jsite.sh --mode start --site-name myproject -``` - -#### 4. 管理网站状态 - -```bash -# 查看状态 -./install_jsite.sh --mode status --site-name myproject - -# 停止网站 -./install_jsite.sh --mode stop --site-name myproject +# 启动网站 +./jsite.sh --mode start --site-name jingrow # 重启网站 -./install_jsite.sh --mode restart --site-name myproject +./jsite.sh --mode restart --site-name jingrow + +# 查看网站状态 +./jsite.sh --mode status --site-name jingrow + +# 查看所有网站 +./jsite.sh --mode list ``` -#### 5. 查看所有网站 +## 故障排除 +### 常见错误及解决方案 + +#### 1. PM2进程未找到错误 + +**错误信息:** +``` +[PM2][ERROR] Process or Namespace jingrow not found +``` + +**原因:** 网站尚未部署或PM2进程未正确启动 + +**解决方案:** +1. 首先检查网站是否存在: + ```bash + ./jsite.sh --mode status --site-name jingrow + ``` + +2. 如果网站不存在,先部署: + ```bash + ./jsite.sh --mode deploy --site-name jingrow + ``` + +3. 如果网站存在但未运行,启动网站: + ```bash + ./jsite.sh --mode start --site-name jingrow + ``` + +#### 2. 权限错误 + +**解决方案:** ```bash -./install_jsite.sh --mode list +# 确保脚本有执行权限 +chmod +x jsite.sh + +# 以root用户运行 +sudo ./jsite.sh --mode start --site-name jingrow ``` -#### 6. 查看日志 +#### 3. 端口冲突 +**解决方案:** ```bash -./install_jsite.sh --mode logs --site-name myproject +# 检查端口占用 +netstat -tlnp | grep :3001 + +# 停止占用端口的进程 +sudo kill -9 <进程ID> ``` -## 工作流程 +### 手动检查和修复 -### 完整部署流程 (deploy) -1. 创建jingrow用户 -2. 安装Docker(可选跳过) -3. 安装Node.js和NVM -4. 克隆项目代码 -5. 创建环境配置文件 -6. 安装项目依赖 -7. 安装PM2并启动项目 -8. 安装Traefik并配置反向代理 +#### 检查PM2状态 +```bash +# 切换到jingrow用户 +su - jingrow -### 网站管理流程 -- **create**: 克隆代码 → 分配端口 → 创建配置 → 提示下一步 -- **build**: 安装依赖 → 构建项目 -- **start**: 检查状态 → 分配端口 → 启动PM2进程 -- **stop**: 停止PM2进程 -- **restart**: 重启PM2进程或启动(如果未运行) -- **delete**: 停止进程 → 清理配置 → 删除文件 +# 加载nvm环境 +export NVM_DIR="$HOME/.nvm" +[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" -## 目录结构 +# 查看PM2进程列表 +pm2 list + +# 查看PM2日志 +pm2 logs +``` + +#### 手动启动PM2进程 +```bash +# 切换到jingrow用户 +su - jingrow + +# 加载nvm环境 +export NVM_DIR="$HOME/.nvm" +[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" + +# 启动进程 +pm2 start jingrow + +# 或者重新加载配置 +pm2 reload jingrow +``` + +## 配置说明 + +### 环境变量 + +脚本支持以下环境变量配置: + +- `SITE_NAME`: 网站名称(默认:jingrow) +- `GIT_REPO`: Git仓库地址 +- `NODE_VERSION`: Node.js版本(默认:22) +- `START_PORT`: 起始端口(默认:3001) +- `SITE_URL`: 网站域名 +- `BACKEND_SERVER_URL`: 后端服务器地址 + +### 目录结构 ``` /home/jingrow/ ├── jsite/ # 网站项目目录 -│ ├── myproject1/ # 项目1 -│ ├── myproject2/ # 项目2 -│ └── site_port.json # 端口分配记录 -├── traefik-docker/ # Traefik配置目录 -│ └── conf.d/website/ # 网站配置文件 -└── .nvm/ # Node.js版本管理 +├── .nvm/ # Node版本管理器 +├── traefik-docker/ # Traefik配置 +└── ... ``` ## 注意事项 -1. **权限要求**:脚本需要root权限运行 -2. **端口管理**:自动分配端口,避免冲突 -3. **环境依赖**:确保系统支持Docker和Node.js -4. **域名配置**:需要正确配置DNS解析 -5. **SSL证书**:Traefik自动申请Let's Encrypt证书 - -## 故障排除 - -### 常见问题 - -1. **端口被占用** - - 检查端口分配文件:`/home/jingrow/jsite/site_port.json` - - 手动修改端口或停止占用进程 - -2. **PM2启动失败** - - 检查项目依赖是否正确安装 - - 查看PM2日志:`pm2 logs <项目名>` - - 确认package.json中有start脚本 - -3. **Traefik配置问题** - - 检查配置文件:`/home/jingrow/traefik-docker/conf.d/website/` - - 重启Traefik服务 - -4. **域名访问问题** - - 确认DNS解析正确 - - 检查防火墙设置 - - 验证SSL证书状态 - -### 日志查看 - -```bash -# PM2日志 -pm2 logs <项目名> - -# Traefik日志 -docker logs - -# 系统日志 -journalctl -u docker -``` +1. 脚本需要以root权限运行 +2. 确保服务器有足够的磁盘空间和内存 +3. 确保网络连接正常,能够访问Git仓库 +4. 建议在生产环境使用前先在测试环境验证 ## 更新日志 -- **v2.0**: 整合jsite.sh功能,支持多种任务模式 -- **v1.0**: 基础部署功能 +### v1.1.0 +- 修复了start_site和restart_site函数的无限递归问题 +- 改进了错误处理和日志输出 +- 优化了PM2进程管理逻辑 -## 许可证 +## 技术支持 -MIT License +如果遇到问题,请检查: +1. 脚本执行日志 +2. PM2进程状态 +3. 系统资源使用情况 +4. 网络连接状态 diff --git a/jsite.sh b/jsite.sh index 5227e30..e48e771 100644 --- a/jsite.sh +++ b/jsite.sh @@ -301,18 +301,50 @@ get_site_status() { return fi - # 检查PM2进程状态 + # 检查PM2进程状态 - 改进的检测逻辑 + local pm2_output=$(su - "jingrow" -c " + export NVM_DIR=\"\$HOME/.nvm\" + [ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\" + pm2 list 2>/dev/null | grep -w '$site_name' || echo 'not_found' + ") + + # 如果没有找到进程 + if [ "$pm2_output" = "not_found" ] || [ -z "$pm2_output" ]; then + echo "stopped" + return + fi + + # 检查进程状态 local pm2_status=$(su - "jingrow" -c " export NVM_DIR=\"\$HOME/.nvm\" [ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\" - pm2 list 2>/dev/null | grep -w '$site_name' | grep -c 'online' || echo '0' + pm2 list 2>/dev/null | grep -w '$site_name' | awk '{print \$10}' || echo 'unknown' ") - if [ "$pm2_status" != "0" ]; then - echo "running" - else - echo "stopped" - fi + # 根据PM2状态返回相应结果 + case "$pm2_status" in + "online") + echo "running" + ;; + "stopped"|"stopping") + echo "stopped" + ;; + "errored"|"error") + echo "error" + ;; + "restarting"|"waiting") + echo "starting" + ;; + *) + # 如果状态未知,进一步检查进程是否真的在运行 + local process_check=$(su - "jingrow" -c " + export NVM_DIR=\"\$HOME/.nvm\" + [ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\" + pm2 list 2>/dev/null | grep -w '$site_name' | grep -q 'online' && echo 'running' || echo 'stopped' + ") + echo "$process_check" + ;; + esac } # 1. 创建jingrow用户 @@ -436,9 +468,6 @@ get_available_port() { # 检查项目是否已有分配的端口 if [ -f "$port_file" ]; then - # 确保jq工具已安装 - ensure_jq_installed - # 使用jq查询已分配的端口 if command -v jq &> /dev/null; then local existing_port=$(jq -r ".$site_name // empty" "$port_file" 2>/dev/null || echo "") @@ -465,15 +494,12 @@ get_available_port() { user_specified_port=true fi - if [ "$user_specified_port" = true ]; then - # 用户传入了自定义端口,优先使用用户指定的端口 - # 检查用户指定的端口是否已被使用 - local port_available=true - if [ -f "$port_file" ]; then - # 确保jq工具已安装 - ensure_jq_installed - - if command -v jq &> /dev/null; then + if [ "$user_specified_port" = true ]; then + # 用户传入了自定义端口,优先使用用户指定的端口 + # 检查用户指定的端口是否已被使用 + local port_available=true + if [ -f "$port_file" ]; then + if command -v jq &> /dev/null; then local used_ports=$(jq -r '.[]' "$port_file" 2>/dev/null || echo "") if echo "$used_ports" | grep -q "^$base_port$"; then port_available=false @@ -492,9 +518,6 @@ get_available_port() { else # 用户指定的端口已被使用,使用最大端口号 + 1 if [ -f "$port_file" ]; then - # 确保jq工具已安装 - ensure_jq_installed - if command -v jq &> /dev/null; then local max_port=$(jq -r 'max(.[])' "$port_file" 2>/dev/null || echo "$base_port") if [ -n "$max_port" ] && [ "$max_port" != "null" ]; then @@ -511,9 +534,6 @@ get_available_port() { else # 用户没有传入自定义端口,使用自动分配逻辑 if [ -f "$port_file" ]; then - # 确保jq工具已安装 - ensure_jq_installed - if command -v jq &> /dev/null; then # 使用jq找到最大端口号 local max_port=$(jq -r 'max(.[])' "$port_file" 2>/dev/null || echo "$base_port") @@ -547,9 +567,6 @@ save_port_assignment() { chown jingrow:jingrow /home/jingrow/jsite fi - # 确保jq工具已安装 - ensure_jq_installed - # 检查jq是否可用 if command -v jq &> /dev/null; then # 使用jq保存到JSON文件 @@ -601,9 +618,6 @@ get_or_assign_port() { local already_saved=false if [ -f "$port_file" ]; then - # 确保jq工具已安装 - ensure_jq_installed - if command -v jq &> /dev/null; then local existing_port=$(jq -r ".$site_name // empty" "$port_file" 2>/dev/null || echo "") if [ -n "$existing_port" ] && [ "$existing_port" != "null" ] && [ "$existing_port" != "empty" ]; then @@ -640,9 +654,6 @@ show_port_assignments() { fi log_info "当前端口分配情况:" - # 确保jq工具已安装 - ensure_jq_installed - if command -v jq &> /dev/null; then # 使用jq格式化输出 jq -r 'to_entries[] | " - \(.key): \(.value)"' "$port_file" 2>/dev/null || log_warning "无法解析端口分配文件" @@ -1142,45 +1153,6 @@ install_docker() { log_success "将jingrow用户添加到docker组" } -# 6.1 安装jq工具(如果未安装) -install_jq() { - log_info "检查jq工具安装状态..." - - if command -v jq &> /dev/null; then - log_warning "jq工具已安装" - else - log_info "开始安装jq工具..." - - # 设置非交互式环境 - export DEBIAN_FRONTEND=noninteractive - export DEBCONF_NONINTERACTIVE_SEEN=true - - # 更新包索引 - apt-get update - - # 安装jq工具 - apt-get install -y --force-yes jq - - if command -v jq &> /dev/null; then - log_success "jq工具安装完成" - else - log_error "jq工具安装失败" - return 1 - fi - fi -} - -# 6.2 确保jq工具已安装 -ensure_jq_installed() { - if ! command -v jq &> /dev/null; then - log_warning "jq工具未安装,正在安装..." - if ! install_jq; then - log_error "jq工具安装失败,无法处理JSON文件" - return 1 - fi - fi -} - # 7. 启动traefik start_traefik() { log_info "启动traefik服务..." @@ -1532,9 +1504,6 @@ delete_site() { # 删除端口分配记录 log_info "删除端口分配记录..." local port_file="$JSITE_BASE_DIR/site_port.json" - # 确保jq工具已安装 - ensure_jq_installed - if [ -f "$port_file" ] && command -v jq &> /dev/null; then if jq "del(.$SITE_NAME)" "$port_file" > "${port_file}.tmp" 2>/dev/null; then mv "${port_file}.tmp" "$port_file" @@ -1604,9 +1573,31 @@ build_site() { # 启动网站 start_site() { + local status=$(get_site_status "$SITE_NAME") + + if [ "$status" = "not_exists" ]; then + log_error "网站 $SITE_NAME 不存在,请先部署网站" + return 1 + fi + + if [ "$status" = "running" ]; then + log_warning "网站 $SITE_NAME 已在运行中" + return 0 + fi + log_info "启动网站: $SITE_NAME" - restart_site - return $? + + # 尝试启动PM2进程 + if ! su - "jingrow" -c " + export NVM_DIR=\"\$HOME/.nvm\" + [ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\" + pm2 start '$SITE_NAME' + "; then + log_error "网站启动失败" + return 1 + fi + + log_success "网站 $SITE_NAME 启动成功" } # 停止网站 @@ -1657,8 +1648,15 @@ restart_site() { fi else # 如果未运行,则启动 - start_site - return $? + log_info "网站未运行,正在启动..." + if ! su - "jingrow" -c " + export NVM_DIR=\"\$HOME/.nvm\" + [ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\" + pm2 start '$SITE_NAME' + "; then + log_error "网站启动失败" + return 1 + fi fi log_success "网站 $SITE_NAME 重启成功" @@ -1679,16 +1677,41 @@ show_site_status() { "stopped") log_warning "网站 $SITE_NAME 已停止" ;; + "error") + log_error "网站 $SITE_NAME 运行出错" + ;; + "starting") + log_warning "网站 $SITE_NAME 正在启动中" + ;; + *) + log_warning "网站 $SITE_NAME 状态未知: $status" + ;; esac # 显示PM2详细状态 if [ "$status" != "not_exists" ]; then - log_info "PM2状态:" + log_info "PM2详细状态:" su - "jingrow" -c " export NVM_DIR=\"\$HOME/.nvm\" [ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\" pm2 list | grep -E '(App name|$SITE_NAME)' || echo '未找到PM2进程' " + + # 显示端口监听状态 + local port_file="/home/jingrow/jsite/site_port.json" + if [ -f "$port_file" ] && command -v jq &> /dev/null; then + local port=$(jq -r ".$SITE_NAME // empty" "$port_file" 2>/dev/null) + if [ -n "$port" ] && [ "$port" != "null" ]; then + log_info "检查端口 $port 监听状态:" + if netstat -tlnp 2>/dev/null | grep -q ":$port "; then + log_success "端口 $port 正在监听" + elif ss -tlnp 2>/dev/null | grep -q ":$port "; then + log_success "端口 $port 正在监听" + else + log_warning "端口 $port 未被监听" + fi + fi + fi fi } @@ -1713,8 +1736,14 @@ list_sites() { "stopped") echo " - $site_name (已停止)" ;; + "error") + echo " - $site_name (运行出错)" + ;; + "starting") + echo " - $site_name (启动中)" + ;; *) - echo " - $site_name (未知状态)" + echo " - $site_name (状态: $status)" ;; esac fi @@ -1789,9 +1818,6 @@ main() { log_warning "跳过Docker安装" fi - # 安装jq工具(用于处理JSON文件) - install_jq - install_nodejs if ! clone_jsite_project; then diff --git a/test_status.sh b/test_status.sh new file mode 100644 index 0000000..da0d264 --- /dev/null +++ b/test_status.sh @@ -0,0 +1,81 @@ +#!/bin/bash + +# 测试修复后的状态检测功能 +echo "=== 测试修复后的状态检测功能 ===" + +# 测试get_site_status函数 +test_get_site_status() { + echo "测试 get_site_status 函数..." + + # 模拟PM2 list输出 + echo "模拟PM2 list输出:" + echo "┌─────┬────────────────┬──────────────┬─────────┬─────────┬──────────┬────────┬──────┬───────────┬──────────┬──────────┬──────────┬──────────┐" + echo "│ id │ name │ namespace │ version │ mode │ pid │ uptime │ ↺ │ status │ cpu │ mem │ user │ watching │" + echo "├─────┼────────────────┼──────────────┼─────────┼─────────┼──────────┼────────┼──────┼───────────┼──────────┼──────────┼──────────┼──────────┤" + echo "│ 0 │ jingrow │ default │ 1.0.0 │ fork │ 12345 │ 2D │ 0 │ online │ 0% │ 45.0mb │ jingrow │ disabled │" + echo "│ 1 │ testapp │ default │ 1.0.0 │ fork │ 12346 │ 0s │ 0 │ stopped │ 0% │ 0b │ jingrow │ disabled │" + echo "│ 2 │ erroredapp │ default │ 1.0.0 │ fork │ 0 │ 0s │ 5 │ errored │ 0% │ 0b │ jingrow │ disabled │" + echo "└─────┴────────────────┴──────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘" + + echo "" + echo "状态解析说明:" + echo "- jingrow: 状态为 'online' -> 应该返回 'running'" + echo "- testapp: 状态为 'stopped' -> 应该返回 'stopped'" + echo "- erroredapp: 状态为 'errored' -> 应该返回 'error'" +} + +# 测试端口检测 +test_port_detection() { + echo "" + echo "=== 测试端口检测功能 ===" + + # 检查常用端口 + local ports=(3001 3002 8080 80 443) + + for port in "${ports[@]}"; do + echo "检查端口 $port:" + if netstat -tlnp 2>/dev/null | grep -q ":$port "; then + echo " ✓ 端口 $port 正在监听 (netstat)" + elif ss -tlnp 2>/dev/null | grep -q ":$port "; then + echo " ✓ 端口 $port 正在监听 (ss)" + else + echo " ✗ 端口 $port 未被监听" + fi + done +} + +# 测试PM2命令可用性 +test_pm2_availability() { + echo "" + echo "=== 测试PM2命令可用性 ===" + + # 检查jingrow用户的PM2 + echo "检查jingrow用户的PM2:" + if su - jingrow -c "command -v pm2" 2>/dev/null; then + echo " ✓ PM2命令可用" + + # 检查PM2版本 + local pm2_version=$(su - jingrow -c "pm2 -v" 2>/dev/null) + if [ -n "$pm2_version" ]; then + echo " ✓ PM2版本: $pm2_version" + else + echo " ✗ 无法获取PM2版本" + fi + + # 检查PM2进程列表 + echo " PM2进程列表:" + su - jingrow -c "pm2 list" 2>/dev/null || echo " ✗ 无法获取PM2进程列表" + else + echo " ✗ PM2命令不可用" + fi +} + +# 运行测试 +test_get_site_status +test_port_detection +test_pm2_availability + +echo "" +echo "=== 测试完成 ===" +echo "现在可以运行修复后的脚本:" +echo " /tmp/jsite.sh --mode status --site-name jingrow" \ No newline at end of file