feat: add Spug auto-deployment scripts for fullstack project
This commit is contained in:
291
spug/QUICKSTART.md
Normal file
291
spug/QUICKSTART.md
Normal file
@@ -0,0 +1,291 @@
|
|||||||
|
# Spug 部署快速参考
|
||||||
|
|
||||||
|
## 🚀 快速开始(5 分钟配置)
|
||||||
|
|
||||||
|
### 1. 上传脚本到 Spug 服务器
|
||||||
|
```bash
|
||||||
|
scp spug/deploy.sh user@spug-server:/opt/spug/scripts/
|
||||||
|
scp spug/hospital-backend.service user@spug-server:/tmp/
|
||||||
|
ssh user@spug-server "chmod +x /opt/spug/scripts/deploy.sh"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 在目标服务器配置 systemd 服务
|
||||||
|
```bash
|
||||||
|
# 登录目标服务器
|
||||||
|
ssh user@target-server
|
||||||
|
|
||||||
|
# 复制服务文件
|
||||||
|
sudo cp /tmp/hospital-backend.service /etc/systemd/system/
|
||||||
|
|
||||||
|
# 启用服务
|
||||||
|
sudo systemctl daemon-reload
|
||||||
|
sudo systemctl enable hospital-backend
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 在 Spug Web 界面配置
|
||||||
|
|
||||||
|
#### 步骤 1: 添加主机
|
||||||
|
- 菜单:**系统管理** -> **主机管理** -> **创建主机**
|
||||||
|
- 填写:
|
||||||
|
- 名称:`production`
|
||||||
|
- IP:你的服务器 IP
|
||||||
|
- 端口:22
|
||||||
|
- 认证:选择密钥或密码
|
||||||
|
|
||||||
|
#### 步骤 2: 创建应用
|
||||||
|
- 菜单:**应用管理** -> **创建应用**
|
||||||
|
- 填写:
|
||||||
|
- 名称:`hospital-performance`
|
||||||
|
- 类型:`其他`
|
||||||
|
- 仓库地址:`https://gitea.gentronhealth.com/chenqi/hospital_performance.git`
|
||||||
|
- 分支:`main`
|
||||||
|
|
||||||
|
#### 步骤 3: 配置环境变量
|
||||||
|
在应用详情页的 **环境变量** 标签页添加:
|
||||||
|
```
|
||||||
|
SPUG_DEPLOY_DIR=/var/www/hospital-performance
|
||||||
|
PYTHON_VERSION=python3.10
|
||||||
|
BACKEND_SERVICE=hospital-backend
|
||||||
|
BACKEND_PORT=8000
|
||||||
|
FRONTEND_SERVICE=nginx
|
||||||
|
LOG_FILE=/var/log/spug/deploy.log
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 步骤 4: 配置发布
|
||||||
|
- 进入应用的 **发布配置** 标签页
|
||||||
|
- 构建类型:`跳过构建`
|
||||||
|
- 发布脚本:`/opt/spug/scripts/deploy.sh`
|
||||||
|
- 保存
|
||||||
|
|
||||||
|
#### 步骤 5: 执行发布
|
||||||
|
- 菜单:**发布管理** -> **创建发布单**
|
||||||
|
- 选择应用和版本
|
||||||
|
- 选择主机 `production`
|
||||||
|
- 点击 **立即发布**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📋 常用命令速查
|
||||||
|
|
||||||
|
### 查看部署日志
|
||||||
|
```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
|
||||||
|
systemctl status nginx
|
||||||
|
|
||||||
|
# 重启服务
|
||||||
|
systemctl restart hospital-backend
|
||||||
|
systemctl restart nginx
|
||||||
|
|
||||||
|
# 查看日志
|
||||||
|
journalctl -u hospital-backend -f
|
||||||
|
```
|
||||||
|
|
||||||
|
### Git 操作
|
||||||
|
```bash
|
||||||
|
# 查看当前版本
|
||||||
|
cd /var/www/hospital-performance
|
||||||
|
git log --oneline -5
|
||||||
|
|
||||||
|
# 回退版本
|
||||||
|
git reset --hard <commit-hash>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 数据库操作
|
||||||
|
```bash
|
||||||
|
# 手动迁移
|
||||||
|
cd /var/www/hospital-performance/backend
|
||||||
|
source ../venv/bin/activate
|
||||||
|
alembic upgrade head
|
||||||
|
|
||||||
|
# 查看迁移历史
|
||||||
|
alembic history
|
||||||
|
|
||||||
|
# 数据库备份
|
||||||
|
pg_dump -h 192.168.110.252 -p 15432 -U postgres hospital_performance > backup.sql
|
||||||
|
|
||||||
|
# 数据库恢复
|
||||||
|
psql -h 192.168.110.252 -p 15432 -U postgres hospital_performance < backup.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
### 前端调试
|
||||||
|
```bash
|
||||||
|
# 检查 Node 版本
|
||||||
|
node -v
|
||||||
|
npm -v
|
||||||
|
|
||||||
|
# 清理重建
|
||||||
|
cd /var/www/hospital-performance/frontend
|
||||||
|
rm -rf node_modules dist
|
||||||
|
npm install --production
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 故障排查流程图
|
||||||
|
|
||||||
|
```
|
||||||
|
部署失败
|
||||||
|
├─ 前置检查失败
|
||||||
|
│ ├─ 缺少命令 → 安装 git/python/node/npm
|
||||||
|
│ └─ 磁盘不足 → 清理空间或扩容
|
||||||
|
│
|
||||||
|
├─ 代码更新失败
|
||||||
|
│ ├─ Git 连接失败 → 检查网络和凭证
|
||||||
|
│ └─ 冲突 → 手动解决或重置
|
||||||
|
│
|
||||||
|
├─ 后端部署失败
|
||||||
|
│ ├─ 虚拟环境 → 删除重建 venv
|
||||||
|
│ ├─ 依赖安装 → 检查 requirements.txt
|
||||||
|
│ ├─ 数据库迁移 → 手动执行 alembic
|
||||||
|
│ └─ 服务启动 → 检查 systemd 配置
|
||||||
|
│
|
||||||
|
└─ 前端部署失败
|
||||||
|
├─ npm install 失败 → 清理缓存 npm cache clean
|
||||||
|
├─ 构建失败 → 检查 Node.js 版本
|
||||||
|
└─ Nginx 加载 → systemctl reload nginx
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚠️ 常见问题速查
|
||||||
|
|
||||||
|
### Q1: 权限错误 "Permission denied"
|
||||||
|
```bash
|
||||||
|
# 修复目录权限
|
||||||
|
sudo chown -R www-data:www-data /var/www/hospital-performance
|
||||||
|
sudo chmod -R 755 /var/www/hospital-performance
|
||||||
|
```
|
||||||
|
|
||||||
|
### Q2: 端口被占用
|
||||||
|
```bash
|
||||||
|
# 查看端口占用
|
||||||
|
sudo lsof -i :8000
|
||||||
|
sudo netstat -tlnp | grep 8000
|
||||||
|
|
||||||
|
# 杀死进程
|
||||||
|
sudo kill -9 <PID>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Q3: 数据库连接失败
|
||||||
|
```bash
|
||||||
|
# 测试连接
|
||||||
|
psql -h 192.168.110.252 -p 15432 -U postgres -d hospital_performance
|
||||||
|
|
||||||
|
# 检查防火墙
|
||||||
|
sudo ufw status
|
||||||
|
sudo telnet 192.168.110.252 15432
|
||||||
|
```
|
||||||
|
|
||||||
|
### Q4: Python 模块找不到
|
||||||
|
```bash
|
||||||
|
# 确认虚拟环境激活
|
||||||
|
source /var/www/hospital-performance/venv/bin/activate
|
||||||
|
|
||||||
|
# 重新安装依赖
|
||||||
|
pip install -r requirements.txt --force-reinstall
|
||||||
|
```
|
||||||
|
|
||||||
|
### Q5: 前端页面空白
|
||||||
|
```bash
|
||||||
|
# 检查浏览器控制台错误
|
||||||
|
F12 -> Console
|
||||||
|
|
||||||
|
# 检查 Nginx 配置
|
||||||
|
sudo nginx -t
|
||||||
|
sudo cat /etc/nginx/sites-available/default
|
||||||
|
|
||||||
|
# 检查文件权限
|
||||||
|
ls -la /var/www/hospital-performance/frontend/dist/
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 监控指标
|
||||||
|
|
||||||
|
### 关键指标
|
||||||
|
- **API 响应时间**: < 200ms
|
||||||
|
- **页面加载时间**: < 3s
|
||||||
|
- **服务可用性**: > 99.9%
|
||||||
|
- **数据库连接数**: < 100
|
||||||
|
|
||||||
|
### 监控命令
|
||||||
|
```bash
|
||||||
|
# CPU 使用率
|
||||||
|
top -bn1 | grep "Cpu(s)"
|
||||||
|
|
||||||
|
# 内存使用
|
||||||
|
free -h
|
||||||
|
|
||||||
|
# 磁盘 IO
|
||||||
|
iostat -x 1
|
||||||
|
|
||||||
|
# 网络流量
|
||||||
|
iftop -P -n
|
||||||
|
|
||||||
|
# 进程监控
|
||||||
|
ps aux | grep uvicorn
|
||||||
|
ps aux | grep nginx
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 发布清单
|
||||||
|
|
||||||
|
### 发布前检查
|
||||||
|
- [ ] 代码已合并到 main 分支
|
||||||
|
- [ ] 通过所有测试
|
||||||
|
- [ ] 数据库迁移脚本就绪
|
||||||
|
- [ ] 备份策略已配置
|
||||||
|
- [ ] 回滚方案已准备
|
||||||
|
|
||||||
|
### 发布中检查
|
||||||
|
- [ ] 代码更新成功
|
||||||
|
- [ ] 依赖安装完成
|
||||||
|
- [ ] 数据库迁移成功
|
||||||
|
- [ ] 服务启动正常
|
||||||
|
- [ ] 健康检查通过
|
||||||
|
|
||||||
|
### 发布后验证
|
||||||
|
- [ ] 访问前端页面
|
||||||
|
- [ ] 测试登录功能
|
||||||
|
- [ ] 验证核心功能
|
||||||
|
- [ ] 检查错误日志
|
||||||
|
- [ ] 监控系统资源
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📞 应急联系
|
||||||
|
|
||||||
|
### 升级流程
|
||||||
|
1. **一级故障**(功能异常)→ 开发团队
|
||||||
|
2. **二级故障**(性能下降)→ 运维团队 + 开发负责人
|
||||||
|
3. **三级故障**(服务中断)→ 紧急响应小组
|
||||||
|
|
||||||
|
### 回滚命令
|
||||||
|
```bash
|
||||||
|
# 快速回滚到上一个版本
|
||||||
|
cd /var/www/hospital-performance
|
||||||
|
git reset --hard HEAD~1
|
||||||
|
|
||||||
|
# 重启服务
|
||||||
|
systemctl restart hospital-backend
|
||||||
|
systemctl restart nginx
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**提示**: 将此文件打印或保存为书签,便于快速查阅!
|
||||||
256
spug/README.md
Normal file
256
spug/README.md
Normal file
@@ -0,0 +1,256 @@
|
|||||||
|
# Spug 自动部署脚本说明
|
||||||
|
|
||||||
|
## 📁 文件说明
|
||||||
|
|
||||||
|
### 1. `deploy.sh` - Bash 版本
|
||||||
|
适用于 Linux/Unix 系统的 Shell 脚本,可在 Spug 的"执行任务"中直接调用。
|
||||||
|
|
||||||
|
**特点:**
|
||||||
|
- 轻量级,无需额外依赖
|
||||||
|
- 直接在目标服务器上执行
|
||||||
|
- 适合简单的部署场景
|
||||||
|
|
||||||
|
### 2. `deploy.py` - Python 版本
|
||||||
|
使用 Python 编写的部署脚本,功能更强大,错误处理更完善。
|
||||||
|
|
||||||
|
**特点:**
|
||||||
|
- 更好的错误处理和日志记录
|
||||||
|
- 支持健康检查(需要 requests 库)
|
||||||
|
- 更适合复杂的部署逻辑
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 在 Spug 中的配置步骤
|
||||||
|
|
||||||
|
### 方案一:使用 Bash 脚本(推荐)
|
||||||
|
|
||||||
|
#### 1. 上传脚本到服务器
|
||||||
|
```bash
|
||||||
|
# 将 deploy.sh 上传到 Spug 服务器
|
||||||
|
scp deploy.sh spug-server:/opt/spug/scripts/
|
||||||
|
chmod +x /opt/spug/scripts/deploy.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. 在 Spug 创建应用
|
||||||
|
- 进入 **应用管理** -> **创建应用**
|
||||||
|
- 填写基本信息:
|
||||||
|
- 应用名称:`hospital-performance`
|
||||||
|
- 应用类型:`其他`
|
||||||
|
- 部署方式:`自定义`
|
||||||
|
|
||||||
|
#### 3. 配置发布设置
|
||||||
|
在应用的 **发布配置** 中:
|
||||||
|
- **构建类型**:选择 `本地构建` 或 `跳过构建`
|
||||||
|
- **发布脚本**:填写 `/opt/spug/scripts/deploy.sh`
|
||||||
|
|
||||||
|
#### 4. 设置环境变量
|
||||||
|
在 Spug 应用的 **环境变量** 中添加:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 项目配置
|
||||||
|
SPUG_APP_NAME=hospital-performance
|
||||||
|
SPUG_DEPLOY_DIR=/var/www/hospital-performance
|
||||||
|
|
||||||
|
# Git 配置
|
||||||
|
SPUG_GIT_REPO=https://gitea.gentronhealth.com/chenqi/hospital_performance.git
|
||||||
|
SPUG_GIT_BRANCH=main
|
||||||
|
|
||||||
|
# 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
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 5. 配置 SSH 连接
|
||||||
|
在 Spug 的 **主机管理** 中添加目标服务器:
|
||||||
|
- 主机名:例如 `production-server`
|
||||||
|
- IP 地址:你的服务器 IP
|
||||||
|
- SSH 端口:22(默认)
|
||||||
|
- 认证方式:密钥或密码
|
||||||
|
|
||||||
|
#### 6. 创建发布单
|
||||||
|
- 进入 **发布管理** -> **创建发布单**
|
||||||
|
- 选择应用和要发布的版本
|
||||||
|
- 选择目标主机
|
||||||
|
- 执行发布
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 方案二:使用 Python 脚本
|
||||||
|
|
||||||
|
#### 1. 安装依赖
|
||||||
|
在 Spug 服务器上安装 Python 依赖:
|
||||||
|
```bash
|
||||||
|
pip3 install requests
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. 上传脚本
|
||||||
|
```bash
|
||||||
|
scp deploy.py spug-server:/opt/spug/scripts/
|
||||||
|
chmod +x /opt/spug/scripts/deploy.py
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3. Spug 配置
|
||||||
|
与 Bash 脚本类似,只需将发布脚本改为:
|
||||||
|
```bash
|
||||||
|
/opt/spug/scripts/deploy.py
|
||||||
|
```
|
||||||
|
|
||||||
|
或在 Spug 执行任务中配置:
|
||||||
|
```bash
|
||||||
|
python3 /opt/spug/scripts/deploy.py
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 systemd 服务配置
|
||||||
|
|
||||||
|
### 后端服务配置示例
|
||||||
|
|
||||||
|
创建 `/etc/systemd/system/hospital-backend.service`:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[Unit]
|
||||||
|
Description=Hospital Performance Backend Service
|
||||||
|
After=network.target postgresql.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=www-data
|
||||||
|
Group=www-data
|
||||||
|
WorkingDirectory=/var/www/hospital-performance/backend
|
||||||
|
Environment="PATH=/var/www/hospital-performance/venv/bin"
|
||||||
|
ExecStart=/var/www/hospital-performance/venv/bin/uvicorn app.main:app --host 0.0.0.0 --port 8000
|
||||||
|
Restart=always
|
||||||
|
RestartSec=10
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
```
|
||||||
|
|
||||||
|
启用服务:
|
||||||
|
```bash
|
||||||
|
sudo systemctl daemon-reload
|
||||||
|
sudo systemctl enable hospital-backend
|
||||||
|
sudo systemctl start hospital-backend
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📝 环境变量说明
|
||||||
|
|
||||||
|
| 变量名 | 说明 | 默认值 |
|
||||||
|
|--------|------|--------|
|
||||||
|
| `SPUG_APP_NAME` | 应用名称 | `hospital-performance` |
|
||||||
|
| `SPUG_DEPLOY_DIR` | 部署目录 | `/var/www/hospital-performance` |
|
||||||
|
| `SPUG_GIT_REPO` | Git 仓库地址 | Gitea 地址 |
|
||||||
|
| `SPUG_GIT_BRANCH` | Git 分支 | `main` |
|
||||||
|
| `PYTHON_VERSION` | Python 版本 | `python3.10` |
|
||||||
|
| `NODE_VERSION` | Node.js 版本 | `18` |
|
||||||
|
| `BACKEND_SERVICE` | 后端服务名 | `hospital-backend` |
|
||||||
|
| `BACKEND_PORT` | 后端端口 | `8000` |
|
||||||
|
| `FRONTEND_SERVICE` | 前端服务名 | `nginx` |
|
||||||
|
| `LOG_FILE` | 日志文件 | `/var/log/spug/deploy.log` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔍 故障排查
|
||||||
|
|
||||||
|
### 查看部署日志
|
||||||
|
```bash
|
||||||
|
tail -f /var/log/spug/deploy.log
|
||||||
|
```
|
||||||
|
|
||||||
|
### 手动执行脚本测试
|
||||||
|
```bash
|
||||||
|
cd /opt/spug/scripts
|
||||||
|
./deploy.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### 检查服务状态
|
||||||
|
```bash
|
||||||
|
systemctl status hospital-backend
|
||||||
|
systemctl status nginx
|
||||||
|
```
|
||||||
|
|
||||||
|
### 常见问题
|
||||||
|
|
||||||
|
#### 1. 权限问题
|
||||||
|
确保脚本有执行权限:
|
||||||
|
```bash
|
||||||
|
chmod +x /opt/spug/scripts/deploy.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. Python 虚拟环境问题
|
||||||
|
删除并重建虚拟环境:
|
||||||
|
```bash
|
||||||
|
rm -rf /var/www/hospital-performance/venv
|
||||||
|
python3.10 -m venv /var/www/hospital-performance/venv
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3. 数据库迁移失败
|
||||||
|
手动执行迁移:
|
||||||
|
```bash
|
||||||
|
cd /var/www/hospital-performance/backend
|
||||||
|
source venv/bin/activate
|
||||||
|
alembic upgrade head
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 4. 前端构建失败
|
||||||
|
检查 Node.js 版本:
|
||||||
|
```bash
|
||||||
|
node -v
|
||||||
|
npm -v
|
||||||
|
```
|
||||||
|
|
||||||
|
清理后重新构建:
|
||||||
|
```bash
|
||||||
|
cd frontend
|
||||||
|
rm -rf node_modules package-lock.json
|
||||||
|
npm install
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 最佳实践
|
||||||
|
|
||||||
|
### 1. 灰度发布
|
||||||
|
先在测试环境验证,再发布到生产环境。
|
||||||
|
|
||||||
|
### 2. 备份策略
|
||||||
|
脚本会自动保留 30 天备份,重要发布前建议手动备份数据库:
|
||||||
|
```bash
|
||||||
|
pg_dump -h localhost -U postgres hospital_performance > backup_$(date +%Y%m%d).sql
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 回滚方案
|
||||||
|
如果发布失败,从备份恢复:
|
||||||
|
```bash
|
||||||
|
# 停止服务
|
||||||
|
systemctl stop hospital-backend
|
||||||
|
|
||||||
|
# 恢复代码
|
||||||
|
cp -r /var/www/hospital-performance/backups/backup_20260228_120000_backend /var/www/hospital-performance/backend
|
||||||
|
|
||||||
|
# 重启服务
|
||||||
|
systemctl start hospital-backend
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 监控告警
|
||||||
|
结合 Spug 的监控功能,配置服务健康检查和告警通知。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📞 技术支持
|
||||||
|
|
||||||
|
如有问题,请联系开发团队或查看项目文档。
|
||||||
389
spug/deploy.py
Normal file
389
spug/deploy.py
Normal file
@@ -0,0 +1,389 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Spug 自动发布脚本 - Python 版本
|
||||||
|
用途:医院绩效考核系统自动化部署(Python 实现)
|
||||||
|
使用场景:在 Spug 的"执行任务"中通过 Python 调用
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import subprocess
|
||||||
|
import shutil
|
||||||
|
from datetime import datetime
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Optional, List
|
||||||
|
|
||||||
|
|
||||||
|
class SpugDeploy:
|
||||||
|
"""Spug 部署类"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
# 配置参数(从环境变量获取,可在 Spug 中设置)
|
||||||
|
self.project_name = os.getenv('SPUG_APP_NAME', 'hospital-performance')
|
||||||
|
self.project_dir = Path(os.getenv('SPUG_DEPLOY_DIR', '/var/www/hospital-performance'))
|
||||||
|
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')
|
||||||
|
self.git_branch = os.getenv('SPUG_GIT_BRANCH', 'main')
|
||||||
|
|
||||||
|
# Python 配置
|
||||||
|
self.python_version = os.getenv('PYTHON_VERSION', 'python3.10')
|
||||||
|
self.venv_dir = self.project_dir / 'venv'
|
||||||
|
|
||||||
|
# Node.js 配置
|
||||||
|
self.node_version = os.getenv('NODE_VERSION', '18')
|
||||||
|
|
||||||
|
# 服务配置
|
||||||
|
self.backend_service = os.getenv('BACKEND_SERVICE', 'hospital-backend')
|
||||||
|
self.backend_port = int(os.getenv('BACKEND_PORT', '8000'))
|
||||||
|
self.frontend_service = os.getenv('FRONTEND_SERVICE', 'nginx')
|
||||||
|
|
||||||
|
# 日志配置
|
||||||
|
log_file = os.getenv('LOG_FILE', '/var/log/spug/deploy.log')
|
||||||
|
self.log_file = Path(log_file)
|
||||||
|
self.deploy_time = datetime.now().strftime('%Y%m%d_%H%M%S')
|
||||||
|
|
||||||
|
# 确保日志目录存在
|
||||||
|
self.log_file.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
def log(self, level: str, message: str):
|
||||||
|
"""记录日志"""
|
||||||
|
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||||
|
log_msg = f"[{timestamp}] [{level}] {message}"
|
||||||
|
print(log_msg)
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(self.log_file, 'a', encoding='utf-8') as f:
|
||||||
|
f.write(log_msg + '\n')
|
||||||
|
except Exception as e:
|
||||||
|
print(f"写入日志失败:{e}")
|
||||||
|
|
||||||
|
def info(self, message: str):
|
||||||
|
self.log("INFO", message)
|
||||||
|
|
||||||
|
def error(self, message: str):
|
||||||
|
self.log("ERROR", message)
|
||||||
|
|
||||||
|
def success(self, message: str):
|
||||||
|
self.log("SUCCESS", message)
|
||||||
|
|
||||||
|
def run_command(self, cmd: List[str], cwd: Optional[Path] = None,
|
||||||
|
check: bool = True, shell: bool = False) -> subprocess.CompletedProcess:
|
||||||
|
"""运行命令"""
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
cmd,
|
||||||
|
cwd=cwd,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
shell=shell,
|
||||||
|
check=check
|
||||||
|
)
|
||||||
|
|
||||||
|
if result.stdout:
|
||||||
|
self.info(result.stdout.strip())
|
||||||
|
if result.stderr:
|
||||||
|
self.info(result.stderr.strip())
|
||||||
|
|
||||||
|
return result
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
self.error(f"命令执行失败:{' '.join(cmd)}")
|
||||||
|
self.error(f"错误信息:{e.stderr}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
def check_command(self, cmd: str) -> bool:
|
||||||
|
"""检查命令是否存在"""
|
||||||
|
return shutil.which(cmd) is not None
|
||||||
|
|
||||||
|
def pre_check(self):
|
||||||
|
"""前置检查"""
|
||||||
|
self.info("========== 开始前置检查 ==========")
|
||||||
|
|
||||||
|
# 检查必要命令
|
||||||
|
required_commands = ['git', 'python3', 'node', 'npm']
|
||||||
|
for cmd in required_commands:
|
||||||
|
if not self.check_command(cmd):
|
||||||
|
self.error(f"命令 {cmd} 未安装,请先安装")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# 检查磁盘空间
|
||||||
|
try:
|
||||||
|
stat = shutil.disk_usage(self.project_dir)
|
||||||
|
available_gb = stat.free / (1024 ** 3)
|
||||||
|
if available_gb < 1:
|
||||||
|
self.error(f"磁盘空间不足 1GB,当前可用:{available_gb:.2f}GB")
|
||||||
|
sys.exit(1)
|
||||||
|
except Exception as e:
|
||||||
|
self.error(f"检查磁盘空间失败:{e}")
|
||||||
|
|
||||||
|
self.success("✓ 前置检查通过")
|
||||||
|
|
||||||
|
def update_code(self):
|
||||||
|
"""更新代码"""
|
||||||
|
self.info("========== 更新代码 ==========")
|
||||||
|
|
||||||
|
os.chdir(self.project_dir)
|
||||||
|
|
||||||
|
git_dir = self.project_dir / '.git'
|
||||||
|
if not git_dir.exists():
|
||||||
|
self.info("首次部署,克隆仓库...")
|
||||||
|
self.run_command(['git', 'clone', self.git_repo, '.'])
|
||||||
|
self.run_command(['git', 'checkout', self.git_branch])
|
||||||
|
else:
|
||||||
|
self.info("更新现有代码...")
|
||||||
|
self.run_command(['git', 'fetch', 'origin', self.git_branch])
|
||||||
|
self.run_command(['git', 'reset', '--hard', f'origin/{self.git_branch}'])
|
||||||
|
self.run_command(['git', 'clean', '-fd'])
|
||||||
|
|
||||||
|
# 获取当前版本信息
|
||||||
|
result = self.run_command(['git', 'rev-parse', '--short', 'HEAD'],
|
||||||
|
capture_output=True, text=True)
|
||||||
|
commit_hash = result.stdout.strip()
|
||||||
|
|
||||||
|
result = self.run_command(['git', 'log', '-1', '--pretty=format:%s'],
|
||||||
|
capture_output=True, text=True)
|
||||||
|
commit_msg = result.stdout.strip()
|
||||||
|
|
||||||
|
self.success(f"✓ 代码更新完成,当前版本:{commit_hash} - {commit_msg}")
|
||||||
|
|
||||||
|
def backup_old_version(self):
|
||||||
|
"""备份旧版本"""
|
||||||
|
self.info("========== 备份当前版本 ==========")
|
||||||
|
|
||||||
|
self.backup_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
backup_name = f"backup_{self.deploy_time}"
|
||||||
|
backup_path = self.backup_dir / backup_name
|
||||||
|
|
||||||
|
# 备份后端
|
||||||
|
if self.backend_dir.exists():
|
||||||
|
backup_backend = backup_path.with_name(f"{backup_name}_backend")
|
||||||
|
shutil.copytree(self.backend_dir, backup_backend, dirs_exist_ok=True)
|
||||||
|
self.info(f"✓ 后端代码已备份到:{backup_backend}")
|
||||||
|
|
||||||
|
# 备份前端构建
|
||||||
|
frontend_dist = self.frontend_dir / 'dist'
|
||||||
|
if frontend_dist.exists():
|
||||||
|
backup_frontend = backup_path.with_name(f"{backup_name}_frontend_dist")
|
||||||
|
shutil.copytree(frontend_dist, backup_frontend, dirs_exist_ok=True)
|
||||||
|
self.info(f"✓ 前端构建已备份到:{backup_frontend}")
|
||||||
|
|
||||||
|
# 清理 30 天前的备份
|
||||||
|
try:
|
||||||
|
for item in self.backup_dir.iterdir():
|
||||||
|
if item.is_dir() and item.name.startswith('backup_'):
|
||||||
|
mtime = datetime.fromtimestamp(item.stat().st_mtime)
|
||||||
|
age = (datetime.now() - mtime).days
|
||||||
|
if age > 30:
|
||||||
|
shutil.rmtree(item)
|
||||||
|
self.info(f"清理过期备份:{item.name}")
|
||||||
|
except Exception as e:
|
||||||
|
self.error(f"清理备份失败:{e}")
|
||||||
|
|
||||||
|
self.success("✓ 备份完成(保留最近 30 天)")
|
||||||
|
|
||||||
|
def deploy_backend(self):
|
||||||
|
"""部署后端"""
|
||||||
|
self.info("========== 部署后端服务 ==========")
|
||||||
|
|
||||||
|
os.chdir(self.backend_dir)
|
||||||
|
|
||||||
|
# 创建虚拟环境
|
||||||
|
if not self.venv_dir.exists():
|
||||||
|
self.info("创建 Python 虚拟环境...")
|
||||||
|
self.run_command([self.python_version, '-m', 'venv', str(self.venv_dir)])
|
||||||
|
|
||||||
|
# 激活虚拟环境(通过设置 PATH)
|
||||||
|
venv_bin = self.venv_dir / 'bin'
|
||||||
|
env = os.environ.copy()
|
||||||
|
env['PATH'] = str(venv_bin) + os.pathsep + env['PATH']
|
||||||
|
env['VIRTUAL_ENV'] = str(self.venv_dir)
|
||||||
|
|
||||||
|
# 升级 pip
|
||||||
|
self.run_command(['pip', 'install', '--upgrade', 'pip'], env=env)
|
||||||
|
|
||||||
|
# 安装依赖
|
||||||
|
self.info("安装 Python 依赖...")
|
||||||
|
self.run_command(['pip', 'install', '-r', 'requirements.txt'], env=env)
|
||||||
|
|
||||||
|
# 数据库迁移
|
||||||
|
alembic_ini = self.backend_dir / 'alembic.ini'
|
||||||
|
if alembic_ini.exists():
|
||||||
|
self.info("执行数据库迁移...")
|
||||||
|
self.run_command(['alembic', 'upgrade', 'head'], env=env)
|
||||||
|
|
||||||
|
# 初始化数据
|
||||||
|
init_db_py = self.backend_dir / 'init_db.py'
|
||||||
|
if init_db_py.exists():
|
||||||
|
self.info("初始化数据库...")
|
||||||
|
try:
|
||||||
|
self.run_command([str(self.venv_dir / 'bin/python'), 'init_db.py'],
|
||||||
|
env=env, check=False)
|
||||||
|
except Exception as e:
|
||||||
|
self.info(f"初始化数据跳过或失败:{e}")
|
||||||
|
|
||||||
|
# 重启后端服务
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
['systemctl', 'list-units', '--type=service', '--all'],
|
||||||
|
capture_output=True,
|
||||||
|
text=True
|
||||||
|
)
|
||||||
|
|
||||||
|
if self.backend_service in result.stdout:
|
||||||
|
self.info(f"重启后端服务...")
|
||||||
|
self.run_command(['systemctl', 'restart', self.backend_service])
|
||||||
|
|
||||||
|
import time
|
||||||
|
time.sleep(2)
|
||||||
|
|
||||||
|
# 检查服务状态
|
||||||
|
result = subprocess.run(
|
||||||
|
['systemctl', 'is-active', self.backend_service],
|
||||||
|
capture_output=True,
|
||||||
|
text=True
|
||||||
|
)
|
||||||
|
|
||||||
|
if result.stdout.strip() == 'active':
|
||||||
|
self.success("✓ 后端服务重启成功")
|
||||||
|
else:
|
||||||
|
self.error("✗ 后端服务启动失败")
|
||||||
|
sys.exit(1)
|
||||||
|
else:
|
||||||
|
self.info("未找到 systemd 服务,跳过重启")
|
||||||
|
except Exception as e:
|
||||||
|
self.error(f"服务管理操作失败:{e}")
|
||||||
|
|
||||||
|
self.success("✓ 后端部署完成")
|
||||||
|
|
||||||
|
def deploy_frontend(self):
|
||||||
|
"""部署前端"""
|
||||||
|
self.info("========== 部署前端服务 ==========")
|
||||||
|
|
||||||
|
os.chdir(self.frontend_dir)
|
||||||
|
|
||||||
|
# 安装依赖
|
||||||
|
self.info("安装 Node.js 依赖...")
|
||||||
|
self.run_command(['npm', 'install', '--production'])
|
||||||
|
|
||||||
|
# 构建前端
|
||||||
|
self.info("构建前端项目...")
|
||||||
|
self.run_command(['npm', 'run', 'build'])
|
||||||
|
|
||||||
|
# 检查构建结果
|
||||||
|
dist_dir = self.frontend_dir / 'dist'
|
||||||
|
if dist_dir.exists() and any(dist_dir.iterdir()):
|
||||||
|
self.success("✓ 前端构建成功")
|
||||||
|
else:
|
||||||
|
self.error("✗ 前端构建失败,dist 目录为空")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# 重新加载 Nginx
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
['systemctl', 'list-units', '--type=service', '--all'],
|
||||||
|
capture_output=True,
|
||||||
|
text=True
|
||||||
|
)
|
||||||
|
|
||||||
|
if self.frontend_service in result.stdout:
|
||||||
|
self.info("重新加载 Nginx...")
|
||||||
|
self.run_command(['systemctl', 'reload', self.frontend_service])
|
||||||
|
except Exception as e:
|
||||||
|
self.error(f"Nginx 操作失败:{e}")
|
||||||
|
|
||||||
|
self.success("✓ 前端部署完成")
|
||||||
|
|
||||||
|
def health_check(self):
|
||||||
|
"""健康检查"""
|
||||||
|
self.info("========== 执行健康检查 ==========")
|
||||||
|
|
||||||
|
import time
|
||||||
|
time.sleep(5)
|
||||||
|
|
||||||
|
# 检查后端 API
|
||||||
|
import requests
|
||||||
|
|
||||||
|
backend_url = f"http://localhost:{self.backend_port}/api/v1/health"
|
||||||
|
try:
|
||||||
|
response = requests.get(backend_url, timeout=10)
|
||||||
|
if response.status_code == 200:
|
||||||
|
self.success("✓ 后端 API 健康检查通过")
|
||||||
|
else:
|
||||||
|
self.error(f"✗ 后端 API 返回异常状态码:{response.status_code}")
|
||||||
|
sys.exit(1)
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
self.error(f"✗ 后端 API 连接失败:{e}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# 检查前端文件
|
||||||
|
index_html = self.frontend_dir / 'dist' / 'index.html'
|
||||||
|
if index_html.exists():
|
||||||
|
self.success("✓ 前端文件存在")
|
||||||
|
else:
|
||||||
|
self.error("✗ 前端文件缺失")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
self.success("✓ 所有健康检查通过")
|
||||||
|
|
||||||
|
def cleanup(self):
|
||||||
|
"""清理临时文件"""
|
||||||
|
self.info("========== 清理临时文件 ==========")
|
||||||
|
|
||||||
|
# 清理 npm 缓存
|
||||||
|
try:
|
||||||
|
self.run_command(['npm', 'cache', 'clean', '--force'], check=False)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 清理 Python 缓存
|
||||||
|
import glob
|
||||||
|
for pycache in glob.glob(str(self.project_dir / '**/__pycache__'), recursive=True):
|
||||||
|
try:
|
||||||
|
shutil.rmtree(pycache)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
for pyc in glob.glob(str(self.project_dir / '**/*.pyc'), recursive=True):
|
||||||
|
try:
|
||||||
|
os.remove(pyc)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
self.success("✓ 清理完成")
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
"""主流程"""
|
||||||
|
self.info("========================================")
|
||||||
|
self.info("Spug 自动发布开始")
|
||||||
|
self.info(f"项目名称:{self.project_name}")
|
||||||
|
self.info(f"部署目录:{self.project_dir}")
|
||||||
|
self.info(f"Git 分支:{self.git_branch}")
|
||||||
|
self.info("========================================")
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.pre_check()
|
||||||
|
self.update_code()
|
||||||
|
self.backup_old_version()
|
||||||
|
self.deploy_backend()
|
||||||
|
self.deploy_frontend()
|
||||||
|
self.health_check()
|
||||||
|
self.cleanup()
|
||||||
|
|
||||||
|
self.info("========================================")
|
||||||
|
self.success("🎉 部署成功完成!")
|
||||||
|
self.info("========================================")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.error(f"部署失败:{e}")
|
||||||
|
import traceback
|
||||||
|
self.error(traceback.format_exc())
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
deployer = SpugDeploy()
|
||||||
|
deployer.run()
|
||||||
284
spug/deploy.sh
Normal file
284
spug/deploy.sh
Normal file
@@ -0,0 +1,284 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Spug 自动发布脚本 - 全栈项目
|
||||||
|
# 用途:医院绩效考核系统自动化部署脚本
|
||||||
|
# 执行方式:在 Spug 的"执行任务"中调用此脚本
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# ==================== 配置参数 ====================
|
||||||
|
# 这些参数可以在 Spug 中通过环境变量传入
|
||||||
|
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_BRANCH="${SPUG_GIT_BRANCH:-main}"
|
||||||
|
|
||||||
|
# Python 虚拟环境
|
||||||
|
VENV_DIR="${PROJECT_DIR}/venv"
|
||||||
|
PYTHON_VERSION="${PYTHON_VERSION:-python3.10}"
|
||||||
|
|
||||||
|
# Node.js 配置
|
||||||
|
NODE_VERSION="${NODE_VERSION:-18}"
|
||||||
|
|
||||||
|
# 服务配置
|
||||||
|
BACKEND_SERVICE="${BACKEND_SERVICE:-hospital-backend}"
|
||||||
|
BACKEND_PORT="${BACKEND_PORT:-8000}"
|
||||||
|
FRONTEND_SERVICE="${FRONTEND_SERVICE:-nginx}"
|
||||||
|
|
||||||
|
# 日志配置
|
||||||
|
LOG_FILE="${LOG_FILE:-/var/log/spug/deploy.log}"
|
||||||
|
DEPLOY_TIME=$(date +"%Y%m%d_%H%M%S")
|
||||||
|
|
||||||
|
# ==================== 工具函数 ====================
|
||||||
|
log() {
|
||||||
|
local level=$1
|
||||||
|
shift
|
||||||
|
local message="$*"
|
||||||
|
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
|
||||||
|
echo "[${timestamp}] [${level}] ${message}" | tee -a "${LOG_FILE}"
|
||||||
|
}
|
||||||
|
|
||||||
|
info() {
|
||||||
|
log "INFO" "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
error() {
|
||||||
|
log "ERROR" "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
success() {
|
||||||
|
log "SUCCESS" "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
check_command() {
|
||||||
|
if ! command -v $1 &> /dev/null; then
|
||||||
|
error "命令 $1 未安装,请先安装"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ==================== 前置检查 ====================
|
||||||
|
pre_check() {
|
||||||
|
info "========== 开始前置检查 =========="
|
||||||
|
|
||||||
|
# 检查必要命令
|
||||||
|
check_command git
|
||||||
|
check_command python3
|
||||||
|
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
|
||||||
|
fi
|
||||||
|
|
||||||
|
info "✓ 前置检查通过"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ==================== 代码更新 ====================
|
||||||
|
update_code() {
|
||||||
|
info "========== 更新代码 =========="
|
||||||
|
|
||||||
|
cd "${PROJECT_DIR}"
|
||||||
|
|
||||||
|
if [ ! -d ".git" ]; then
|
||||||
|
info "首次部署,克隆仓库..."
|
||||||
|
git clone "${GIT_REPO}" .
|
||||||
|
git checkout "${GIT_BRANCH}"
|
||||||
|
else
|
||||||
|
info "更新现有代码..."
|
||||||
|
git fetch origin "${GIT_BRANCH}"
|
||||||
|
git reset --hard "origin/${GIT_BRANCH}"
|
||||||
|
git clean -fd
|
||||||
|
fi
|
||||||
|
|
||||||
|
local commit_hash=$(git rev-parse --short HEAD)
|
||||||
|
local commit_msg=$(git log -1 --pretty=format:"%s")
|
||||||
|
info "✓ 代码更新完成,当前版本:${commit_hash} - ${commit_msg}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ==================== 备份旧版本 ====================
|
||||||
|
backup_old_version() {
|
||||||
|
info "========== 备份当前版本 =========="
|
||||||
|
|
||||||
|
mkdir -p "${BACKUP_DIR}"
|
||||||
|
local backup_name="backup_${DEPLOY_TIME}"
|
||||||
|
local backup_path="${BACKUP_DIR}/${backup_name}"
|
||||||
|
|
||||||
|
# 备份关键目录
|
||||||
|
if [ -d "${BACKEND_DIR}" ]; then
|
||||||
|
cp -r "${BACKEND_DIR}" "${backup_path}_backend"
|
||||||
|
info "✓ 后端代码已备份到:${backup_path}_backend"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -d "${FRONTEND_DIR}/dist" ]; then
|
||||||
|
cp -r "${FRONTEND_DIR}/dist" "${backup_path}_frontend_dist"
|
||||||
|
info "✓ 前端构建已备份到:${backup_path}_frontend_dist"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 清理 30 天前的备份
|
||||||
|
find "${BACKUP_DIR}" -type d -name "backup_*" -mtime +30 -exec rm -rf {} \; 2>/dev/null || true
|
||||||
|
|
||||||
|
info "✓ 备份完成(保留最近 30 天)"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ==================== 后端部署 ====================
|
||||||
|
deploy_backend() {
|
||||||
|
info "========== 部署后端服务 =========="
|
||||||
|
|
||||||
|
cd "${BACKEND_DIR}"
|
||||||
|
|
||||||
|
# 创建虚拟环境
|
||||||
|
if [ ! -d "${VENV_DIR}" ]; then
|
||||||
|
info "创建 Python 虚拟环境..."
|
||||||
|
${PYTHON_VERSION} -m venv "${VENV_DIR}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 激活虚拟环境
|
||||||
|
source "${VENV_DIR}/bin/activate"
|
||||||
|
|
||||||
|
# 升级 pip
|
||||||
|
pip install --upgrade pip -q
|
||||||
|
|
||||||
|
# 安装依赖
|
||||||
|
info "安装 Python 依赖..."
|
||||||
|
pip install -r requirements.txt -q
|
||||||
|
|
||||||
|
# 数据库迁移
|
||||||
|
if [ -f "alembic.ini" ]; then
|
||||||
|
info "执行数据库迁移..."
|
||||||
|
alembic upgrade head
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 初始化数据(如果存在)
|
||||||
|
if [ -f "init_db.py" ]; then
|
||||||
|
info "初始化数据库..."
|
||||||
|
python init_db.py || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 重启后端服务
|
||||||
|
if systemctl list-units --type=service --all | grep -q "${BACKEND_SERVICE}"; then
|
||||||
|
info "重启后端服务..."
|
||||||
|
systemctl restart "${BACKEND_SERVICE}"
|
||||||
|
sleep 2
|
||||||
|
|
||||||
|
# 检查服务状态
|
||||||
|
if systemctl is-active --quiet "${BACKEND_SERVICE}"; then
|
||||||
|
success "✓ 后端服务重启成功"
|
||||||
|
else
|
||||||
|
error "✗ 后端服务启动失败"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
info "未找到 systemd 服务,跳过重启"
|
||||||
|
fi
|
||||||
|
|
||||||
|
deactivate
|
||||||
|
info "✓ 后端部署完成"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ==================== 前端部署 ====================
|
||||||
|
deploy_frontend() {
|
||||||
|
info "========== 部署前端服务 =========="
|
||||||
|
|
||||||
|
cd "${FRONTEND_DIR}"
|
||||||
|
|
||||||
|
# 安装依赖
|
||||||
|
info "安装 Node.js 依赖..."
|
||||||
|
npm install --production
|
||||||
|
|
||||||
|
# 构建前端
|
||||||
|
info "构建前端项目..."
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
# 检查构建结果
|
||||||
|
if [ -d "dist" ] && [ "$(ls -A dist)" ]; then
|
||||||
|
success "✓ 前端构建成功"
|
||||||
|
else
|
||||||
|
error "✗ 前端构建失败,dist 目录为空"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 如果使用 Nginx,重新加载配置
|
||||||
|
if systemctl list-units --type=service --all | grep -q "${FRONTEND_SERVICE}"; then
|
||||||
|
info "重新加载 Nginx..."
|
||||||
|
systemctl reload "${FRONTEND_SERVICE}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
info "✓ 前端部署完成"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ==================== 健康检查 ====================
|
||||||
|
health_check() {
|
||||||
|
info "========== 执行健康检查 =========="
|
||||||
|
|
||||||
|
# 等待服务启动
|
||||||
|
sleep 5
|
||||||
|
|
||||||
|
# 检查后端 API
|
||||||
|
local backend_url="http://localhost:${BACKEND_PORT}/api/v1/health"
|
||||||
|
if curl -f -s "${backend_url}" > /dev/null; then
|
||||||
|
success "✓ 后端 API 健康检查通过"
|
||||||
|
else
|
||||||
|
error "✗ 后端 API 健康检查失败"
|
||||||
|
# 回滚逻辑(可选)
|
||||||
|
# rollback
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 检查前端
|
||||||
|
if [ -d "${FRONTEND_DIR}/dist/index.html" ]; then
|
||||||
|
success "✓ 前端文件存在"
|
||||||
|
else
|
||||||
|
error "✗ 前端文件缺失"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
info "✓ 所有健康检查通过"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ==================== 清理工作 ====================
|
||||||
|
cleanup() {
|
||||||
|
info "========== 清理临时文件 =========="
|
||||||
|
|
||||||
|
# 清理 npm 缓存
|
||||||
|
npm cache clean --force 2>/dev/null || true
|
||||||
|
|
||||||
|
# 清理 Python 缓存
|
||||||
|
find "${PROJECT_DIR}" -type d -name "__pycache__" -exec rm -rf {} \; 2>/dev/null || true
|
||||||
|
find "${PROJECT_DIR}" -type f -name "*.pyc" -delete 2>/dev/null || true
|
||||||
|
|
||||||
|
info "✓ 清理完成"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ==================== 主流程 ====================
|
||||||
|
main() {
|
||||||
|
info "========================================"
|
||||||
|
info "Spug 自动发布开始"
|
||||||
|
info "项目名称:${PROJECT_NAME}"
|
||||||
|
info "部署目录:${PROJECT_DIR}"
|
||||||
|
info "Git 分支:${GIT_BRANCH}"
|
||||||
|
info "========================================"
|
||||||
|
|
||||||
|
# 执行部署流程
|
||||||
|
pre_check
|
||||||
|
update_code
|
||||||
|
backup_old_version
|
||||||
|
deploy_backend
|
||||||
|
deploy_frontend
|
||||||
|
health_check
|
||||||
|
cleanup
|
||||||
|
|
||||||
|
info "========================================"
|
||||||
|
success "🎉 部署成功完成!"
|
||||||
|
info "========================================"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 执行主流程
|
||||||
|
main "$@"
|
||||||
40
spug/hospital-backend.service
Normal file
40
spug/hospital-backend.service
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Hospital Performance Backend Service
|
||||||
|
Documentation=https://gitea.gentronhealth.com/chenqi/hospital_performance
|
||||||
|
After=network.target postgresql.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=www-data
|
||||||
|
Group=www-data
|
||||||
|
|
||||||
|
# 工作目录
|
||||||
|
WorkingDirectory=/var/www/hospital-performance/backend
|
||||||
|
|
||||||
|
# 环境变量
|
||||||
|
Environment="PATH=/var/www/hospital-performance/venv/bin"
|
||||||
|
Environment="DATABASE_URL=postgresql+asyncpg://postgresql:Jchl1528@192.168.110.252:15432/hospital_performance"
|
||||||
|
Environment="SECRET_KEY=your-secret-key-change-in-production"
|
||||||
|
Environment="DEBUG=False"
|
||||||
|
|
||||||
|
# 启动命令
|
||||||
|
ExecStart=/var/www/hospital-performance/venv/bin/uvicorn app.main:app \
|
||||||
|
--host 0.0.0.0 \
|
||||||
|
--port 8000 \
|
||||||
|
--workers 4 \
|
||||||
|
--access-log
|
||||||
|
|
||||||
|
# 重启策略
|
||||||
|
Restart=always
|
||||||
|
RestartSec=10
|
||||||
|
|
||||||
|
# 资源限制(可选)
|
||||||
|
LimitNOFILE=65535
|
||||||
|
|
||||||
|
# 日志配置
|
||||||
|
StandardOutput=journal
|
||||||
|
StandardError=journal
|
||||||
|
SyslogIdentifier=hospital-backend
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
Reference in New Issue
Block a user