#!/bin/bash # Spug 自动发布脚本 - 全栈项目 # 用途:医院绩效考核系统自动化部署脚本 # 执行方式:在 Spug 的"执行任务"中调用此脚本 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}" BACKUP_DIR="${PROJECT_DIR}/backups" FRONTEND_DIR="${PROJECT_DIR}/frontend" BACKEND_DIR="${PROJECT_DIR}/backend" # 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 虚拟环境 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}" # 日志配置 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") # ==================== 工具函数 ==================== 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 # 检查部署目录 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 "✓ 前置检查通过" } # ==================== 代码更新 ==================== update_code() { info "========== 更新代码 ==========" cd "${PROJECT_DIR}" 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 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 "$@"