Files
hospital_performance/spug/deploy-docker.sh

318 lines
10 KiB
Bash
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/bin/bash
# Spug 自动部署脚本 - Docker 版本
# 用途:医院绩效考核系统 Docker 自动化部署
# 执行方式:在 Spug 的"执行任务"中调用此脚本
set -e
# ==================== 配置参数 ====================
# 项目配置
PROJECT_NAME="${SPUG_APP_NAME:-hospital_performance}"
PROJECT_DIR="${SPUG_DEPLOY_DIR:-/var/www/hospital-performance}"
DOCKER_REGISTRY="${DOCKER_REGISTRY:-}" # 可选:私有仓库地址
DOCKER_IMAGE_NAME="${DOCKER_IMAGE_NAME:-hospital_performance}"
DOCKER_TAG="${DOCKER_TAG:-latest}"
# Git 配置
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}"
# Docker 配置
DOCKER_NETWORK="${DOCKER_NETWORK:-hospital_network}"
DOCKER_BACKEND_PORT="${DOCKER_BACKEND_PORT:-8000}"
DOCKER_FRONTEND_PORT="${DOCKER_FRONTEND_PORT:-80}"
CONTAINER_NAME="${CONTAINER_NAME:-hospital_performance}"
# 数据库配置(从环境变量获取)
DATABASE_HOST="${DATABASE_HOST:-192.168.110.252}"
DATABASE_PORT="${DATABASE_PORT:-15432}"
DATABASE_USER="${DATABASE_USER:-postgresql}"
DATABASE_PASSWORD="${DATABASE_PASSWORD:-Jchl1528}"
DATABASE_NAME="${DATABASE_NAME:-hospital_performance}"
# JWT 配置
SECRET_KEY="${SECRET_KEY:-change-this-secret-key-in-production}"
DEBUG="${DEBUG:-False}"
# 日志配置
if [ -z "${LOG_FILE}" ]; then
LOG_FILE="/var/log/spug/deploy-docker.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 docker
# 检查 Docker 是否运行
if ! docker info > /dev/null 2>&1; then
error "Docker 未运行,请启动 Docker 服务"
exit 1
fi
# 检查部署目录
if [ ! -d "${PROJECT_DIR}" ]; then
info "创建部署目录:${PROJECT_DIR}"
mkdir -p "${PROJECT_DIR}"
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")
success "✓ 代码更新完成,当前版本:${commit_hash} - ${commit_msg}"
}
# ==================== 构建 Docker 镜像 ====================
build_docker_image() {
info "========== 构建 Docker 镜像 =========="
cd "${PROJECT_DIR}"
# 检查 Dockerfile 是否存在
if [ ! -f "Dockerfile" ]; then
error "Dockerfile 不存在,无法构建镜像"
exit 1
fi
# 构建后端镜像
info "构建后端 Docker 镜像..."
docker build -t ${DOCKER_IMAGE_NAME}-backend:${DOCKER_TAG} \
-f Dockerfile.backend . \
--build-arg DATABASE_HOST=${DATABASE_HOST} \
--build-arg DATABASE_PORT=${DATABASE_PORT} \
--build-arg SECRET_KEY=${SECRET_KEY} \
--build-arg DEBUG=${DEBUG}
# 构建前端镜像(如果有)
if [ -d "frontend" ] && [ -f "Dockerfile.frontend" ]; then
info "构建前端 Docker 镜像..."
docker build -t ${DOCKER_IMAGE_NAME}-frontend:${DOCKER_TAG} \
-f Dockerfile.frontend .
fi
# 推送到私有仓库(如果配置了)
if [ -n "${DOCKER_REGISTRY}" ]; then
info "推送镜像到私有仓库:${DOCKER_REGISTRY}"
docker tag ${DOCKER_IMAGE_NAME}-backend:${DOCKER_TAG} \
${DOCKER_REGISTRY}/${DOCKER_IMAGE_NAME}-backend:${DOCKER_TAG}
docker push ${DOCKER_REGISTRY}/${DOCKER_IMAGE_NAME}-backend:${DOCKER_TAG}
if [ -d "frontend" ] && [ -f "Dockerfile.frontend" ]; then
docker tag ${DOCKER_IMAGE_NAME}-frontend:${DOCKER_TAG} \
${DOCKER_REGISTRY}/${DOCKER_IMAGE_NAME}-frontend:${DOCKER_TAG}
docker push ${DOCKER_REGISTRY}/${DOCKER_IMAGE_NAME}-frontend:${DOCKER_TAG}
fi
fi
success "✓ Docker 镜像构建完成"
}
# ==================== 停止旧容器 ====================
stop_old_containers() {
info "========== 停止旧容器 =========="
# 停止并删除旧容器
if docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}-backend$"; then
info "停止后端容器:${CONTAINER_NAME}-backend"
docker stop "${CONTAINER_NAME}-backend" || true
docker rm "${CONTAINER_NAME}-backend" || true
fi
if docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}-frontend$"; then
info "停止前端容器:${CONTAINER_NAME}-frontend"
docker stop "${CONTAINER_NAME}-frontend" || true
docker rm "${CONTAINER_NAME}-frontend" || true
fi
success "✓ 旧容器已清理"
}
# ==================== 启动新容器 ====================
start_new_containers() {
info "========== 启动新容器 =========="
# 创建网络(如果不存在)
if ! docker network ls --format '{{.Name}}' | grep -q "^${DOCKER_NETWORK}$"; then
info "创建 Docker 网络:${DOCKER_NETWORK}"
docker network create ${DOCKER_NETWORK}
fi
# 启动后端容器
info "启动后端容器..."
docker run -d \
--name ${CONTAINER_NAME}-backend \
--network ${DOCKER_NETWORK} \
-p ${DOCKER_BACKEND_PORT}:8000 \
-e DATABASE_URL="postgresql+asyncpg://${DATABASE_USER}:${DATABASE_PASSWORD}@${DATABASE_HOST}:${DATABASE_PORT}/${DATABASE_NAME}" \
-e SECRET_KEY=${SECRET_KEY} \
-e DEBUG=${DEBUG} \
-v ${PROJECT_DIR}/backend/logs:/app/logs \
--restart unless-stopped \
${DOCKER_IMAGE_NAME}-backend:${DOCKER_TAG}
# 等待后端启动
info "等待后端服务启动..."
sleep 10
# 启动前端容器(如果有)
if [ -d "frontend" ] && [ -f "Dockerfile.frontend" ]; then
info "启动前端容器..."
docker run -d \
--name ${CONTAINER_NAME}-frontend \
--network ${DOCKER_NETWORK} \
-p ${DOCKER_FRONTEND_PORT}:80 \
--link ${CONTAINER_NAME}-backend:backend \
--restart unless-stopped \
${DOCKER_IMAGE_NAME}-frontend:${DOCKER_TAG}
fi
success "✓ 容器启动成功"
}
# ==================== 健康检查 ====================
health_check() {
info "========== 执行健康检查 =========="
# 等待服务启动
sleep 5
# 检查后端 API
local backend_url="http://localhost:${DOCKER_BACKEND_PORT}/api/v1/health"
local max_attempts=10
local attempt=1
while [ $attempt -le $max_attempts ]; do
if curl -f -s "${backend_url}" > /dev/null 2>&1; then
success "✓ 后端 API 健康检查通过"
break
else
if [ $attempt -eq $max_attempts ]; then
error "✗ 后端 API 健康检查失败(尝试 ${attempt}/${max_attempts}"
exit 1
fi
info "后端服务未就绪,等待 5 秒后重试... (${attempt}/${max_attempts})"
sleep 5
attempt=$((attempt + 1))
fi
done
# 检查容器状态
if docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}-backend$"; then
success "✓ 后端容器运行正常"
else
error "✗ 后端容器未运行"
exit 1
fi
if [ -d "frontend" ] && [ -f "Dockerfile.frontend" ]; then
if docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}-frontend$"; then
success "✓ 前端容器运行正常"
fi
fi
info "✓ 所有健康检查通过"
}
# ==================== 清理旧镜像 ====================
cleanup_old_images() {
info "========== 清理旧 Docker 镜像 =========="
# 保留最近 3 个版本的镜像
docker images ${DOCKER_IMAGE_NAME}-backend --format "{{.Tag}}" | \
tail -n +4 | xargs -r docker rmi 2>/dev/null || true
if [ -f "Dockerfile.frontend" ]; then
docker images ${DOCKER_IMAGE_NAME}-frontend --format "{{.Tag}}" | \
tail -n +4 | xargs -r docker rmi 2>/dev/null || true
fi
# 清理悬空镜像
docker image prune -f
info "✓ 清理完成"
}
# ==================== 主流程 ====================
main() {
info "========================================"
info "Spug Docker 自动部署开始"
info "项目名称:${PROJECT_NAME}"
info "部署目录:${PROJECT_DIR}"
info "Git 分支:${GIT_BRANCH}"
info "Docker 镜像:${DOCKER_IMAGE_NAME}:${DOCKER_TAG}"
info "========================================"
pre_check
update_code
build_docker_image
stop_old_containers
start_new_containers
health_check
cleanup_old_images
info "========================================"
success "🎉 Docker 部署成功完成!"
info "后端地址http://localhost:${DOCKER_BACKEND_PORT}"
if [ -d "frontend" ] && [ -f "Dockerfile.frontend" ]; then
info "前端地址http://localhost:${DOCKER_FRONTEND_PORT}"
fi
info "========================================"
}
main "$@"