#!/bin/bash set -e # 蓝绿部署脚本(Caddy 版本) # 用法: ./deploy.sh [blue|green] SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" cd "$SCRIPT_DIR" # 颜色定义 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"; } # 自动发现 Docker 网络名(兼容 docker compose 前缀) detect_network() { local net net=$(docker network ls --format "{{.Name}}" | grep -E '^(gch3n-infra_)?gch3n-network$' | head -1 || true) if [ -z "$net" ]; then # 回退到默认 bridge,避免脚本直接失败 net="bridge" log_warn "未发现 gch3n-network,回退使用 bridge 网络" fi echo "$net" } # 获取当前活跃的环境 get_active_color() { local active=$(docker ps --filter "name=app-" --format "{{.Names}}" | grep -E "app-(blue|green)" | head -1) if [ -z "$active" ]; then echo "none" else echo "$active" | sed 's/app-//' fi } # 健康检查(主机侧) check_health() { local color=$1 local port=$2 local max_attempts=30 local attempt=0 log_info "检查 $color 健康状态 (127.0.0.1:$port)..." while [ $attempt -lt $max_attempts ]; do if curl -fsS "http://127.0.0.1:$port/health" >/dev/null 2>&1; then log_info "$color 健康检查通过 ✓" return 0 fi attempt=$((attempt + 1)) log_warn "健康检查尝试 $attempt/$max_attempts 失败,等待 2 秒..." sleep 2 done log_error "$color 健康检查失败 ✗" return 1 } # 部署到指定环境 deploy_to() { local target_color=$1 local target_port=$2 local other_color=$3 local network_name=$4 log_info "开始部署到 $target_color 环境..." # 1. 构建新镜像 log_info "构建 Docker 镜像..." docker build -t "gch3n/app:$target_color" -t "gch3n/app:latest" ./app # 2. 停止目标环境(如果存在) if docker ps -q --filter "name=app-$target_color" | grep -q .; then log_warn "停止现有的 $target_color 容器..." docker stop "app-$target_color" || true docker rm "app-$target_color" || true fi # 3. 启动新容器 log_info "启动 $target_color 容器(network=$network_name)..." docker run -d \ --name "app-$target_color" \ --network "$network_name" \ -p "127.0.0.1:$target_port:$target_port" \ -e "PORT=$target_port" \ -e "DEPLOY_COLOR=$target_color" \ --health-cmd="wget --quiet --tries=1 --spider http://localhost:$target_port/health || exit 1" \ --health-interval=10s \ --health-timeout=5s \ --health-retries=3 \ --health-start-period=10s \ --restart=unless-stopped \ "gch3n/app:$target_color" # 4. 健康检查 if ! check_health "$target_color" "$target_port"; then log_error "$target_color 部署失败,执行回滚..." docker stop "app-$target_color" || true docker rm "app-$target_color" || true if [ "$other_color" != "none" ]; then log_warn "保持 $other_color 环境运行" fi exit 1 fi # 5. 停止旧环境(Caddy 会自动只转发健康后端) if [ "$other_color" != "none" ] && [ "$other_color" != "$target_color" ]; then log_info "停止旧环境 $other_color..." docker stop "app-$other_color" || true docker rm "app-$other_color" || true fi log_info "部署完成!当前活跃环境: $target_color" log_info "Caddy 会自动反代到健康实例,访问: https://app.gch3n.online" } # 主逻辑 main() { log_info "========================================" log_info " gch3n.online 蓝绿部署脚本 (Caddy)" log_info "========================================" local network_name network_name=$(detect_network) # 获取当前活跃环境 local active=$(get_active_color) log_info "当前活跃环境: $active" # 确定目标环境 local target=$1 if [ -z "$target" ]; then # 自动选择非活跃环境 if [ "$active" = "blue" ]; then target="green" else target="blue" fi log_info "自动选择目标环境: $target" fi # 验证参数 if [ "$target" != "blue" ] && [ "$target" != "green" ]; then log_error "无效的环境: $target,只能是 blue 或 green" exit 1 fi # 确定端口 local target_port=3001 [ "$target" = "green" ] && target_port=3002 # 执行部署 deploy_to "$target" "$target_port" "$active" "$network_name" } # 执行 main "$@"