diff --git a/spug/.env.example b/spug/.env.example new file mode 100644 index 0000000..d81e63b --- /dev/null +++ b/spug/.env.example @@ -0,0 +1,68 @@ +# Spug 环境变量配置示例 +# 在 Spug 应用的"环境变量"中配置以下参数 + +# ==================== 必需配置 ==================== + +# 部署目录(目标服务器上的路径) +SPUG_DEPLOY_DIR=/var/www/hospital-performance + +# Git 仓库地址(Spug 会自动设置,也可手动指定) +# SPUG_GIT_URL=https://gitea.gentronhealth.com/chenqi/hospital_performance.git + +# Git 分支 +SPUG_GIT_BRANCH=main + + +# ==================== 可选配置 ==================== + +# 应用名称 +SPUG_APP_NAME=hospital-performance + +# Python 版本 +PYTHON_VERSION=python3.10 + +# Node.js 版本 +NODE_VERSION=18 + +# 后端服务名称 +BACKEND_SERVICE=hospital-backend + +# 后端端口 +BACKEND_PORT=8000 + +# 前端服务名称 +FRONTEND_SERVICE=nginx + +# 日志文件路径 +LOG_FILE=/var/log/spug/deploy.log + + +# ==================== 数据库配置 ==================== + +# PostgreSQL 连接(生产环境请使用更安全的密钥管理) +DATABASE_HOST=192.168.110.252 +DATABASE_PORT=15432 +DATABASE_USER=postgresql +DATABASE_PASSWORD=Jchl1528 +DATABASE_NAME=hospital_performance + + +# ==================== JWT 配置 ==================== + +# JWT 密钥(生产环境必须修改!) +SECRET_KEY=your-super-secret-key-change-in-production-min-32-chars + +# 调试模式(生产环境设为 False) +DEBUG=False + + +# ==================== 通知配置(可选) ==================== + +# 钉钉机器人 Webhook +# DINGTALK_WEBHOOK=https://oapi.dingtalk.com/robot/send?access_token=xxx + +# 企业微信机器人 Webhook +# WECHAT_WORK_WEBHOOK=https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxx + +# 邮件通知 +# NOTIFY_EMAIL=admin@example.com diff --git a/spug/DEPLOYMENT_GUIDE.md b/spug/DEPLOYMENT_GUIDE.md new file mode 100644 index 0000000..a0fba67 --- /dev/null +++ b/spug/DEPLOYMENT_GUIDE.md @@ -0,0 +1,369 @@ +# Spug 自动部署配置指南 + +## 🚀 快速开始(10 分钟完成) + +### 步骤 1: 上传脚本到 Spug 服务器 + +```bash +# 从本地上传 +scp spug/deploy.sh user@spug-server:/opt/spug/scripts/ +scp spug/deploy.py user@spug-server:/opt/spug/scripts/optional/ +scp spug/hospital-backend.service user@target-server:/tmp/ + +# 设置权限 +ssh user@spug-server "chmod +x /opt/spug/scripts/deploy.sh" +``` + +### 步骤 2: 在目标服务器配置 systemd 服务 + +登录目标服务器(`user@target-server`): + +```bash +# 复制服务文件 +sudo cp /tmp/hospital-backend.service /etc/systemd/system/ + +# 重新加载 systemd +sudo systemctl daemon-reload + +# 启用服务(先不启动,等部署后再启动) +sudo systemctl enable hospital-backend + +# 创建部署目录 +sudo mkdir -p /var/www/hospital-performance +sudo chown -R $USER:$USER /var/www/hospital-performance +``` + +### 步骤 3: 在 Spug Web 界面配置 + +#### 3.1 添加主机 + +路径:**系统管理** -> **主机管理** -> **创建主机** + +``` +名称:production +IP: <你的目标服务器 IP> +端口:22 +认证方式:密钥 或 密码 +``` + +#### 3.2 创建应用 + +路径:**应用管理** -> **创建应用** + +``` +应用名称:hospital-performance +应用类型:其他 +Git 仓库:https://gitea.gentronhealth.com/chenqi/hospital_performance.git +分支:main +``` + +#### 3.3 配置环境变量 + +在应用详情页的 **环境变量** 标签页,添加以下变量: + +**必需配置:** +``` +SPUG_DEPLOY_DIR=/var/www/hospital-performance +SPUG_GIT_BRANCH=main +``` + +**可选配置:** +``` +PYTHON_VERSION=python3.10 +BACKEND_SERVICE=hospital-backend +BACKEND_PORT=8000 +FRONTEND_SERVICE=nginx +LOG_FILE=/var/log/spug/deploy.log +``` + +**数据库配置(如果需要在部署时修改):** +``` +DATABASE_HOST=192.168.110.252 +DATABASE_PORT=15432 +DATABASE_USER=postgresql +DATABASE_PASSWORD=Jchl1528 +DATABASE_NAME=hospital_performance +SECRET_KEY=<生成一个随机密钥> +DEBUG=False +``` + +#### 3.4 配置发布设置 + +在应用的 **发布配置** 标签页: + +``` +构建类型:跳过构建 +发布脚本:/opt/spug/scripts/deploy.sh +``` + +### 步骤 4: 执行首次发布 + +路径:**发布管理** -> **创建发布单** + +1. 选择应用:`hospital-performance` +2. 选择版本:选择最新的 commit +3. 选择主机:`production` +4. 点击 **立即发布** + +--- + +## 📋 验证部署 + +### 查看部署日志 + +在目标服务器上: + +```bash +# 实时查看 +tail -f /var/log/spug/deploy.log + +# 查看最近 100 行 +tail -n 100 /var/log/spug/deploy.log + +# 搜索错误 +grep ERROR /var/log/spug/deploy.log +``` + +### 检查服务状态 + +```bash +# 后端服务 +systemctl status hospital-backend + +# 前端 Nginx +systemctl status nginx + +# 查看进程 +ps aux | grep uvicorn +ps aux | grep nginx +``` + +### 测试 API + +```bash +# 健康检查 +curl http://localhost:8000/api/v1/health + +# 登录测试 +curl -X POST http://localhost:8000/api/v1/auth/login \ + -H "Content-Type: application/json" \ + -d '{"username":"admin","password":"admin123"}' +``` + +### 访问前端 + +浏览器访问:`http://<服务器 IP>` + +应该能看到登录页面。 + +--- + +## 🔧 常见问题排查 + +### 问题 1: 环境变量为空 + +**现象:** 日志显示 `PROJECT_NAME:` 和 `DEPLOY_DIR:` 为空 + +**原因:** Spug 没有正确传递环境变量 + +**解决:** +1. 检查应用的"环境变量"是否正确配置 +2. 确保 `SPUG_DEPLOY_DIR` 不为空 +3. 脚本已做容错处理,会自动使用默认值 + +### 问题 2: Git 克隆失败 + +**现象:** `fatal: destination path '.' already exists and is not an empty directory.` + +**原因:** 部署目录非空且没有 .git 目录 + +**解决:** +- 脚本已自动处理,会备份现有文件后克隆 +- 或者手动清理部署目录:`rm -rf /var/www/hospital-performance/*` + +### 问题 3: 权限错误 + +**现象:** `Permission denied` + +**解决:** +```bash +# 修复目录权限 +sudo chown -R $USER:$USER /var/www/hospital-performance +sudo chmod -R 755 /var/www/hospital-performance +``` + +### 问题 4: 服务启动失败 + +**现象:** `systemctl restart hospital-backend` 失败 + +**排查:** +```bash +# 查看详细日志 +journalctl -u hospital-backend -n 50 + +# 检查端口占用 +sudo lsof -i :8000 + +# 手动测试启动 +cd /var/www/hospital-performance/backend +source venv/bin/activate +uvicorn app.main:app --host 0.0.0.0 --port 8000 +``` + +### 问题 5: 数据库连接失败 + +**现象:** `asyncpg.exceptions.ConnectionRefusedError` + +**解决:** +```bash +# 测试数据库连接 +psql -h 192.168.110.252 -p 15432 -U postgres -d hospital_performance + +# 如果连不上,检查防火墙 +sudo ufw status +sudo telnet 192.168.110.252 15432 +``` + +--- + +## 🎯 自动化流程说明 + +### 完整部署流程 + +``` +Spug 触发发布 + ↓ +1. 前置检查(命令、磁盘空间) + ↓ +2. 代码更新(Git clone/pull) + ↓ +3. 备份旧版本(保留 30 天) + ↓ +4. 后端部署 + ├─ 创建/激活虚拟环境 + ├─ 安装 Python 依赖 + ├─ 数据库迁移 + ├─ 重启 systemd 服务 + ↓ +5. 前端部署 + ├─ 安装 Node 依赖 + ├─ 构建前端 + ├─ 重载 Nginx + ↓ +6. 健康检查 + ├─ API 可用性 + ├─ 前端文件完整性 + ↓ +7. 清理临时文件 + ↓ +部署成功! +``` + +### 回滚流程 + +如果部署失败,可以: + +```bash +# 1. 停止服务 +systemctl stop hospital-backend + +# 2. 恢复代码 +cd /var/www/hospital-performance +cp -r backups/backup_YYYYMMDD_HHMMSS_backend backend +cp -r backups/backup_YYYYMMDD_HHMMSS_frontend_dist frontend/dist + +# 3. 恢复数据库(如果需要) +psql -h 192.168.110.252 -p 15432 -U postgres hospital_performance < backup.sql + +# 4. 重启服务 +systemctl start hospital-backend +systemctl reload nginx +``` + +--- + +## 📊 监控与告警 + +### 关键指标 + +- **API 响应时间**: < 200ms +- **页面加载时间**: < 3s +- **服务可用性**: > 99.9% +- **错误率**: < 0.1% + +### 监控命令 + +```bash +# CPU 使用率 +top -bn1 | grep "Cpu(s)" + +# 内存使用 +free -h + +# 磁盘 IO +iostat -x 1 + +# 网络流量 +iftop -P -n + +# 数据库连接数 +psql -h 192.168.110.252 -p 15432 -U postgres -d hospital_performance -c "SELECT count(*) FROM pg_stat_activity;" +``` + +### 日志聚合 + +```bash +# 后端日志 +journalctl -u hospital-backend -f + +# Nginx 访问日志 +tail -f /var/log/nginx/access.log + +# Nginx 错误日志 +tail -f /var/log/nginx/error.log + +# Spug 部署日志 +tail -f /var/log/spug/deploy.log +``` + +--- + +## 🔄 持续集成建议 + +### Git 工作流 + +``` +main 分支(生产) + ↑ +develop 分支(测试) + ↑ +feature/* 分支(开发) +``` + +### 发布频率 + +- **小功能/修复**: 每天 1-2 次 +- **大版本**: 每周或每两周一次 +- **紧急修复**: 随时发布 + +### 最佳实践 + +1. ✅ **代码审查**: 所有合并到 main 的代码必须经过 review +2. ✅ **测试环境**: 先在 develop 分支测试,再发布到生产 +3. ✅ **备份策略**: 数据库每天自动备份 +4. ✅ **灰度发布**: 重要变更先在小范围测试 +5. ✅ **监控告警**: 配置钉钉/企业微信通知 + +--- + +## 📞 技术支持 + +遇到问题时: + +1. 查看部署日志:`/var/log/spug/deploy.log` +2. 查看服务日志:`journalctl -u hospital-backend` +3. 联系开发团队或查看项目文档 + +--- + +**提示**: 将此文档保存为书签,便于快速查阅! diff --git a/spug/deploy.py b/spug/deploy.py index 4ca1f4c..b9b973f 100644 --- a/spug/deploy.py +++ b/spug/deploy.py @@ -19,15 +19,25 @@ class SpugDeploy: def __init__(self): # 配置参数(从环境变量获取,可在 Spug 中设置) + # 处理 SPUG_DEPLOY_DIR 为空的情况 + deploy_dir = os.getenv('SPUG_DEPLOY_DIR', '').strip() + if not deploy_dir: + deploy_dir = '/var/www/hospital-performance' + self.project_name = os.getenv('SPUG_APP_NAME', 'hospital-performance') - self.project_dir = Path(os.getenv('SPUG_DEPLOY_DIR', '/var/www/hospital-performance')) + self.project_dir = Path(deploy_dir) self.backup_dir = self.project_dir / 'backups' self.frontend_dir = self.project_dir / 'frontend' self.backend_dir = self.project_dir / 'backend' - # Git 配置 - self.git_repo = os.getenv('SPUG_GIT_REPO', - 'https://gitea.gentronhealth.com/chenqi/hospital_performance.git') + # Git 配置(优先使用 Spug 的环境变量) + git_repo = os.getenv('SPUG_GIT_URL', '').strip() + if not git_repo: + git_repo = os.getenv('SPUG_GIT_REPO', '').strip() + if not git_repo: + git_repo = 'https://gitea.gentronhealth.com/chenqi/hospital_performance.git' + self.git_repo = git_repo + self.git_branch = os.getenv('SPUG_GIT_BRANCH', 'main') # Python 配置 @@ -110,6 +120,11 @@ class SpugDeploy: self.error(f"命令 {cmd} 未安装,请先安装") sys.exit(1) + # 创建部署目录(如果不存在) + if not self.project_dir.exists(): + self.info(f"创建部署目录:{self.project_dir}") + self.project_dir.mkdir(parents=True, exist_ok=True) + # 检查磁盘空间 try: stat = shutil.disk_usage(self.project_dir) @@ -131,6 +146,21 @@ class SpugDeploy: git_dir = self.project_dir / '.git' if not git_dir.exists(): self.info("首次部署,克隆仓库...") + + # 检查目录是否为空 + if any(self.project_dir.iterdir()): + self.info("目录非空,先备份现有文件...") + backup_non_git = self.project_dir / f"backup_{datetime.now().strftime('%Y%m%d_%H%M%S')}_non_git" + backup_non_git.mkdir(exist_ok=True) + + # 移动非 .git 相关文件 + for item in self.project_dir.iterdir(): + if item.name not in ['.git', 'backups', 'venv'] and not item.name.startswith('backup_'): + try: + shutil.move(str(item), str(backup_non_git / item.name)) + except Exception as e: + self.info(f"跳过文件 {item.name}: {e}") + self.run_command(['git', 'clone', self.git_repo, '.']) self.run_command(['git', 'checkout', self.git_branch]) else: diff --git a/spug/deploy.sh b/spug/deploy.sh index f8cf48f..7a22e3d 100644 --- a/spug/deploy.sh +++ b/spug/deploy.sh @@ -7,14 +7,27 @@ set -e # ==================== 配置参数 ==================== # 这些参数可以在 Spug 中通过环境变量传入 +# 如果 SPUG_DEPLOY_DIR 为空,则使用默认值 +if [ -z "${SPUG_DEPLOY_DIR}" ]; then + PROJECT_DIR="/var/www/hospital-performance" +else + PROJECT_DIR="${SPUG_DEPLOY_DIR}" +fi + PROJECT_NAME="${SPUG_APP_NAME:-hospital-performance}" -PROJECT_DIR="${SPUG_DEPLOY_DIR:-/var/www/hospital-performance}" BACKUP_DIR="${PROJECT_DIR}/backups" FRONTEND_DIR="${PROJECT_DIR}/frontend" BACKEND_DIR="${PROJECT_DIR}/backend" -# Git 配置 -GIT_REPO="${SPUG_GIT_REPO:-https://gitea.gentronhealth.com/chenqi/hospital_performance.git}" +# Git 配置(Spug 会自动设置 SPUG_GIT_URL) +if [ -n "${SPUG_GIT_URL}" ]; then + GIT_REPO="${SPUG_GIT_URL}" +elif [ -n "${SPUG_GIT_REPO}" ]; then + GIT_REPO="${SPUG_GIT_REPO}" +else + GIT_REPO="https://gitea.gentronhealth.com/chenqi/hospital_performance.git" +fi + GIT_BRANCH="${SPUG_GIT_BRANCH:-main}" # Python 虚拟环境 @@ -30,7 +43,11 @@ BACKEND_PORT="${BACKEND_PORT:-8000}" FRONTEND_SERVICE="${FRONTEND_SERVICE:-nginx}" # 日志配置 -LOG_FILE="${LOG_FILE:-/var/log/spug/deploy.log}" +if [ -z "${LOG_FILE}" ]; then + LOG_FILE="/var/log/spug/deploy.log" + # 确保日志目录存在 + mkdir -p /var/log/spug 2>/dev/null || true +fi DEPLOY_TIME=$(date +"%Y%m%d_%H%M%S") # ==================== 工具函数 ==================== @@ -71,11 +88,19 @@ pre_check() { check_command node check_command npm - # 检查磁盘空间 - local available_space=$(df -P "${PROJECT_DIR}" | awk 'NR==2 {print $4}') - if [ "${available_space}" -lt 1048576 ]; then - error "磁盘空间不足 1GB,当前可用:${available_space}KB" - exit 1 + # 检查部署目录 + if [ ! -d "${PROJECT_DIR}" ]; then + info "创建部署目录:${PROJECT_DIR}" + mkdir -p "${PROJECT_DIR}" + fi + + # 检查磁盘空间(如果目录存在) + if [ -d "${PROJECT_DIR}" ]; then + local available_space=$(df -P "${PROJECT_DIR}" 2>/dev/null | awk 'NR==2 {print $4}') + if [ -n "${available_space}" ] && [ "${available_space}" -lt 1048576 ]; then + error "磁盘空间不足 1GB,当前可用:${available_space}KB" + exit 1 + fi fi info "✓ 前置检查通过" @@ -89,6 +114,12 @@ update_code() { if [ ! -d ".git" ]; then info "首次部署,克隆仓库..." + # 确保目录为空或不存在 + if [ "$(ls -A ${PROJECT_DIR} 2>/dev/null)" ]; then + info "目录非空,先备份现有文件..." + mkdir -p "${PROJECT_DIR}/backup_$(date +%Y%m%d_%H%M%S)_non_git" + find "${PROJECT_DIR}" -maxdepth 1 -type f -o -type d ! -name '.' -exec mv {} "${PROJECT_DIR}/backup_$(date +%Y%m%d_%H%M%S)_non_git/" \; 2>/dev/null || true + fi git clone "${GIT_REPO}" . git checkout "${GIT_BRANCH}" else