316 lines
8.6 KiB
Bash
316 lines
8.6 KiB
Bash
#!/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 "$@"
|