| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263 |
- #!/bin/bash
- # ============================================================
- # 智慧水务管理系统 - 部署脚本
- # 用法:
- # ./scripts/deploy.sh --env testing --host testing.example.com --user deploy --tag feature-abc12345
- # ./scripts/deploy.sh --notify --env production --status success --webhook "https://..." --branch main --commit abc123 --project "智慧水务"
- # ============================================================
- set -euo pipefail
-
- # ==================== 默认配置 ====================
- ENV=""
- HOST=""
- USER="deploy"
- TAG="latest"
- SSH_KEY_PATH="${SSH_KEY_PATH:-~/.ssh/id_rsa}"
- COMPOSE_FILE="docker-compose.yml"
- HEALTH_TIMEOUT=120
- HEALTH_INTERVAL=5
- ROLLBACK_ON_FAILURE=true
-
- # 通知参数
- NOTIFY_MODE=false
- STATUS=""
- WEBHOOK=""
- BRANCH=""
- COMMIT=""
- PROJECT=""
-
- # ==================== 参数解析 ====================
- while [[ $# -gt 0 ]]; do
- case $1 in
- --env) ENV="$2"; shift 2 ;;
- --host) HOST="$2"; shift 2 ;;
- --user) USER="$2"; shift 2 ;;
- --tag) TAG="$2"; shift 2 ;;
- --ssh-key) SSH_KEY_PATH="$2"; shift 2 ;;
- --compose-file) COMPOSE_FILE="$2"; shift 2 ;;
- --health-timeout) HEALTH_TIMEOUT="$2"; shift 2 ;;
- --no-rollback) ROLLBACK_ON_FAILURE=false; shift ;;
- --notify) NOTIFY_MODE=true; shift ;;
- --status) STATUS="$2"; shift 2 ;;
- --webhook) WEBHOOK="$2"; shift 2 ;;
- --branch) BRANCH="$2"; shift 2 ;;
- --commit) COMMIT="$2"; shift 2 ;;
- --project) PROJECT="$2"; shift 2 ;;
- -h|--help)
- echo "用法: $0 [选项]"
- echo ""
- echo "部署模式:"
- echo " --env ENV 部署环境: testing | production"
- echo " --host HOST 目标服务器地址"
- echo " --user USER SSH 用户 (默认: deploy)"
- echo " --tag TAG 镜像标签 (默认: latest)"
- echo " --ssh-key PATH SSH 私钥路径"
- echo " --compose-file FILE docker-compose 文件 (默认: docker-compose.yml)"
- echo " --health-timeout SEC 健康检查超时秒数 (默认: 120)"
- echo " --no-rollback 禁用失败自动回滚"
- echo ""
- echo "通知模式 (--notify):"
- echo " --status STATUS 部署状态: success | failure"
- echo " --webhook URL 企业微信 Webhook URL"
- echo " --branch BRANCH Git 分支名"
- echo " --commit SHA Git 提交 SHA"
- echo " --project NAME 项目名称"
- exit 0
- ;;
- *)
- echo "❌ 未知参数: $1"
- exit 1
- ;;
- esac
- done
-
- # ==================== 通知函数 ====================
- send_notification() {
- if [ -z "$WEBHOOK" ] || [ "$WEBHOOK" = "" ]; then
- echo "⚠️ 未配置 Webhook URL,跳过通知"
- return 0
- fi
-
- local emoji="✅"
- local status_text="部署成功"
- if [ "$STATUS" = "failure" ]; then
- emoji="❌"
- status_text="部署失败"
- fi
-
- local commit_short="${COMMIT:0:8}"
-
- # 企业微信 Markdown 消息格式
- local payload
- payload=$(cat <<EOF
- {
- "msgtype": "markdown",
- "markdown": {
- "content": "## ${emoji} ${PROJECT:-智慧水务管理系统} 部署通知\n\n**环境:** ${ENV:-unknown}\n**分支:** ${BRANCH:-unknown}\n**提交:** ${commit_short:-unknown}\n**状态:** <font color=\"$([ "$STATUS" = "success" ] && echo "info" || echo "warning")\">${status_text}</font>\n**时间:** $(date '+%Y-%m-%d %H:%M:%S')"
- }
- }
- EOF
- )
-
- echo "📤 发送部署通知..."
- local response
- response=$(curl -s -w "\n%{http_code}" -X POST "$WEBHOOK" \
- -H "Content-Type: application/json" \
- -d "$payload" 2>/dev/null || echo -e "\n000")
-
- local http_code
- http_code=$(echo "$response" | tail -1)
- local body
- body=$(echo "$response" | head -n -1)
-
- if [ "$http_code" = "200" ]; then
- echo "✅ 通知发送成功"
- else
- echo "⚠️ 通知发送失败 (HTTP ${http_code}): ${body}"
- fi
- }
-
- # ==================== 健康检查 ====================
- wait_for_healthy() {
- local host="$1"
- local timeout="$2"
- local elapsed=0
-
- echo "⏳ 等待服务健康检查通过 (超时: ${timeout}s)..."
-
- while [ $elapsed -lt $timeout ]; do
- # 检查 docker compose 服务状态
- local unhealthy
- unhealthy=$(ssh -i "$SSH_KEY_PATH" -o StrictHostKeyChecking=no "${USER}@${host}" \
- "docker compose -f ${COMPOSE_FILE} ps --format '{{.Service}} {{.Health}}' 2>/dev/null | grep -v 'healthy' | grep -v 'running' | head -5" 2>/dev/null || echo "")
-
- if [ -z "$unhealthy" ]; then
- echo "✅ 所有服务健康运行 (${elapsed}s)"
- return 0
- fi
-
- echo " 等待中... (${elapsed}s / ${timeout}s)"
- sleep "$HEALTH_INTERVAL"
- elapsed=$((elapsed + HEALTH_INTERVAL))
- done
-
- echo "❌ 健康检查超时 (${timeout}s)"
- echo " 未健康的服务:"
- echo " $unhealthy"
- return 1
- }
-
- # ==================== 回滚 ====================
- rollback() {
- local host="$1"
- echo "🔄 执行回滚..."
-
- ssh -i "$SSH_KEY_PATH" -o StrictHostKeyChecking=no "${USER}@${host}" bash <<ROLLBACK
- set -e
- cd /opt/water-management 2>/dev/null || cd ~/water-management
-
- echo "回滚到上一个版本..."
- # docker compose 会自动保留上一版本镜像
- docker compose -f ${COMPOSE_FILE} up -d --force-recreate 2>/dev/null || {
- echo "❌ 回滚失败!请手动检查"
- exit 1
- }
-
- echo "✅ 回滚完成"
- ROLLBACK
- }
-
- # ==================== 部署 ====================
- deploy() {
- if [ -z "$ENV" ]; then
- echo "❌ 必须指定 --env 参数 (testing|production)"
- exit 1
- fi
-
- if [ -z "$HOST" ]; then
- echo "❌ 必须指定 --host 参数"
- exit 1
- fi
-
- echo "========================================="
- echo " 智慧水务管理系统 - 部署"
- echo " 环境: ${ENV}"
- echo " 主机: ${HOST}"
- echo " 标签: ${TAG}"
- echo "========================================="
-
- # 1. 记录当前版本(用于回滚)
- echo ""
- echo "📋 记录当前版本..."
- ssh -i "$SSH_KEY_PATH" -o StrictHostKeyChecking=no "${USER}@${HOST}" bash <<'SAVE_VERSION'
- cd /opt/water-management 2>/dev/null || cd ~/water-management
- mkdir -p .deploy
- docker compose -f docker-compose.yml config --images 2>/dev/null | sort > .deploy/previous_images.txt || true
- echo "VERSION=$(date +%Y%m%d%H%M%S)" > .deploy/previous_version.txt
- SAVE_VERSION
-
- # 2. 更新环境变量中的镜像标签
- echo ""
- echo "📝 更新镜像标签为: ${TAG}"
- ssh -i "$SSH_KEY_PATH" -o StrictHostKeyChecking=no "${USER}@${HOST}" bash <<UPDATE_TAG
- cd /opt/water-management 2>/dev/null || cd ~/water-management
- # 更新 .env 中的 IMAGE_TAG
- if [ -f .env ]; then
- sed -i "s/^IMAGE_TAG=.*/IMAGE_TAG=${TAG}/" .env 2>/dev/null || echo "IMAGE_TAG=${TAG}" >> .env
- else
- echo "IMAGE_TAG=${TAG}" > .env
- fi
- echo "IMAGE_TAG=${TAG}"
- UPDATE_TAG
-
- # 3. 拉取新镜像
- echo ""
- echo "📥 拉取新镜像..."
- ssh -i "$SSH_KEY_PATH" -o StrictHostKeyChecking=no "${USER}@${HOST}" bash <<PULL
- cd /opt/water-management 2>/dev/null || cd ~/water-management
- docker compose -f ${COMPOSE_FILE} pull 2>&1 || {
- echo "❌ 镜像拉取失败"
- exit 1
- }
- PULL
-
- # 4. 启动服务
- echo ""
- echo "🚀 启动服务..."
- ssh -i "$SSH_KEY_PATH" -o StrictHostKeyChecking=no "${USER}@${HOST}" bash <<UP
- cd /opt/water-management 2>/dev/null || cd ~/water-management
- docker compose -f ${COMPOSE_FILE} up -d --remove-orphans 2>&1 || {
- echo "❌ 服务启动失败"
- exit 1
- }
- UP
-
- # 5. 健康检查
- echo ""
- if wait_for_healthy "$HOST" "$HEALTH_TIMEOUT"; then
- echo ""
- echo "========================================="
- echo " ✅ 部署成功!"
- echo " 环境: ${ENV}"
- echo " 标签: ${TAG}"
- echo "========================================="
- return 0
- else
- echo ""
- echo "========================================="
- echo " ❌ 部署失败!"
- echo "========================================="
-
- if [ "$ROLLBACK_ON_FAILURE" = true ]; then
- rollback "$HOST"
- fi
- return 1
- fi
- }
-
- # ==================== 主逻辑 ====================
- if [ "$NOTIFY_MODE" = true ]; then
- send_notification
- else
- deploy
- fi
|