diff --git a/deploy/scripts/deploy-tenant.sh b/deploy/scripts/deploy-tenant.sh new file mode 100644 index 0000000..79048c9 --- /dev/null +++ b/deploy/scripts/deploy-tenant.sh @@ -0,0 +1,218 @@ +#!/bin/bash +# ================================================================ +# 租户后端部署脚本 +# 用于创建/更新租户的后端容器,确保包含所有必要的挂载 +# +# 使用方法: +# ./deploy-tenant.sh [image_tag] +# +# 示例: +# ./deploy-tenant.sh ex main # 部署 ex 租户,使用 main 镜像 +# ./deploy-tenant.sh hua # 部署 hua 租户,默认 main 镜像 +# ./deploy-tenant.sh all # 部署所有租户 +# +# 注意: +# - 此脚本需要在服务器 120.79.247.16 上执行 +# - 确保 secrets 目录存在:/data/prod-envs/secrets/coze_private_key.pem +# ================================================================ + +set -e + +# 配置 +IMAGE_REGISTRY="crpi-na6dit5kd0bonqed.cn-guangzhou.personal.cr.aliyuncs.com/ireborn/kaopeilian-backend" +ENV_DIR="/root/aiedu/kaopeilian-backend" +DATA_DIR="/data/prod-envs" +SECRETS_DIR="/data/prod-envs/secrets" + +# 租户配置(端口映射) +declare -A TENANT_PORTS=( + ["hua"]="8010" + ["yy"]="8011" + ["hl"]="8012" + ["xy"]="8013" + ["fw"]="8014" + ["ex"]="8015" + ["cxw"]="8016" +) + +# 所有租户列表 +ALL_TENANTS=("hua" "yy" "hl" "xy" "fw" "ex" "cxw") + +# 颜色输出 +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +log_info() { echo -e "${GREEN}[INFO]${NC} $1"; } +log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } +log_error() { echo -e "${RED}[ERROR]${NC} $1"; } + +# 检查前置条件 +check_prerequisites() { + log_info "检查前置条件..." + + # 检查 Docker + if ! docker info > /dev/null 2>&1; then + log_error "Docker 未运行" + exit 1 + fi + + # 检查 secrets 目录 + if [ ! -f "${SECRETS_DIR}/coze_private_key.pem" ]; then + log_error "私钥文件不存在: ${SECRETS_DIR}/coze_private_key.pem" + exit 1 + fi + + log_info "前置条件检查通过" +} + +# 部署单个租户 +deploy_tenant() { + local tenant=$1 + local image_tag=${2:-main} + local port=${TENANT_PORTS[$tenant]} + + if [ -z "$port" ]; then + log_error "未知租户: $tenant" + return 1 + fi + + local container_name="${tenant}-backend" + local env_file="${ENV_DIR}/.env.${tenant}" + local image="${IMAGE_REGISTRY}:${image_tag}" + + log_info "==========================================" + log_info "部署租户: ${tenant}" + log_info "容器名: ${container_name}" + log_info "端口: ${port}:8000" + log_info "镜像: ${image}" + log_info "==========================================" + + # 检查环境变量文件 + if [ ! -f "$env_file" ]; then + log_error "环境变量文件不存在: $env_file" + return 1 + fi + + # 创建必要的目录 + mkdir -p "${DATA_DIR}/uploads-${tenant}" + mkdir -p "${DATA_DIR}/logs-${tenant}" + + # 拉取最新镜像 + log_info "拉取镜像..." + docker pull "$image" || { + log_warn "拉取镜像失败,使用本地镜像" + } + + # 停止并删除旧容器 + if docker ps -a --format '{{.Names}}' | grep -q "^${container_name}$"; then + log_info "停止旧容器..." + docker stop "$container_name" 2>/dev/null || true + docker rm "$container_name" 2>/dev/null || true + fi + + # 创建新容器 + log_info "创建新容器..." + docker run -d \ + --name "$container_name" \ + --restart unless-stopped \ + --env-file "$env_file" \ + -e TZ=Asia/Shanghai \ + -e PYTHONPATH=/app \ + -e WORKERS=4 \ + -e RELOAD=false \ + -p "${port}:8000" \ + -v "${DATA_DIR}/uploads-${tenant}:/app/uploads:rw" \ + -v "${DATA_DIR}/logs-${tenant}:/app/logs:rw" \ + -v "${SECRETS_DIR}:/app/secrets:ro" \ + -v /etc/localtime:/etc/localtime:ro \ + -v /etc/timezone:/etc/timezone:ro \ + --network kaopeilian-network \ + --label "com.centurylinklabs.watchtower.enable=false" \ + "$image" + + # 连接到 prod-network + log_info "连接网络..." + docker network connect prod-network "$container_name" 2>/dev/null || true + + # 等待容器启动 + log_info "等待容器启动..." + sleep 5 + + # 检查容器状态 + local status=$(docker inspect "$container_name" --format '{{.State.Status}}' 2>/dev/null) + if [ "$status" = "running" ]; then + log_info "✓ ${container_name} 部署成功" + + # 验证 secrets 挂载 + if docker exec "$container_name" ls /app/secrets/coze_private_key.pem > /dev/null 2>&1; then + log_info "✓ secrets 挂载验证成功" + else + log_warn "✗ secrets 挂载验证失败" + fi + else + log_error "✗ ${container_name} 启动失败,状态: $status" + docker logs "$container_name" --tail 20 + return 1 + fi +} + +# 部署所有租户 +deploy_all_tenants() { + local image_tag=${1:-main} + + log_info "部署所有租户 (镜像标签: $image_tag)" + + for tenant in "${ALL_TENANTS[@]}"; do + deploy_tenant "$tenant" "$image_tag" + echo "" + done + + log_info "所有租户部署完成" +} + +# 显示帮助 +show_help() { + echo "用法: $0 [image_tag]" + echo "" + echo "租户代码:" + echo " hua - 华尔倍丽" + echo " yy - 杨扬宠物" + echo " hl - 武汉禾丽" + echo " xy - 芯颜定制" + echo " fw - 飞沃" + echo " ex - 恩喜成都总院" + echo " cxw - 崔曦文" + echo " all - 所有租户" + echo "" + echo "镜像标签 (可选, 默认: main):" + echo " main - 生产环境" + echo " staging - 预发布环境" + echo " test - 测试环境" + echo "" + echo "示例:" + echo " $0 ex main # 部署 ex 租户" + echo " $0 all # 部署所有租户" +} + +# 主函数 +main() { + local tenant=${1:-} + local image_tag=${2:-main} + + if [ -z "$tenant" ] || [ "$tenant" = "-h" ] || [ "$tenant" = "--help" ]; then + show_help + exit 0 + fi + + check_prerequisites + + if [ "$tenant" = "all" ]; then + deploy_all_tenants "$image_tag" + else + deploy_tenant "$tenant" "$image_tag" + fi +} + +main "$@"