智慧水务管理系统 - 精河县供水工程综合管理平台

deploy.sh 8.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. #!/bin/bash
  2. # ============================================================
  3. # 智慧水务管理系统 - 部署脚本
  4. # 用法:
  5. # ./scripts/deploy.sh --env testing --host testing.example.com --user deploy --tag feature-abc12345
  6. # ./scripts/deploy.sh --notify --env production --status success --webhook "https://..." --branch main --commit abc123 --project "智慧水务"
  7. # ============================================================
  8. set -euo pipefail
  9. # ==================== 默认配置 ====================
  10. ENV=""
  11. HOST=""
  12. USER="deploy"
  13. TAG="latest"
  14. SSH_KEY_PATH="${SSH_KEY_PATH:-~/.ssh/id_rsa}"
  15. COMPOSE_FILE="docker-compose.yml"
  16. HEALTH_TIMEOUT=120
  17. HEALTH_INTERVAL=5
  18. ROLLBACK_ON_FAILURE=true
  19. # 通知参数
  20. NOTIFY_MODE=false
  21. STATUS=""
  22. WEBHOOK=""
  23. BRANCH=""
  24. COMMIT=""
  25. PROJECT=""
  26. # ==================== 参数解析 ====================
  27. while [[ $# -gt 0 ]]; do
  28. case $1 in
  29. --env) ENV="$2"; shift 2 ;;
  30. --host) HOST="$2"; shift 2 ;;
  31. --user) USER="$2"; shift 2 ;;
  32. --tag) TAG="$2"; shift 2 ;;
  33. --ssh-key) SSH_KEY_PATH="$2"; shift 2 ;;
  34. --compose-file) COMPOSE_FILE="$2"; shift 2 ;;
  35. --health-timeout) HEALTH_TIMEOUT="$2"; shift 2 ;;
  36. --no-rollback) ROLLBACK_ON_FAILURE=false; shift ;;
  37. --notify) NOTIFY_MODE=true; shift ;;
  38. --status) STATUS="$2"; shift 2 ;;
  39. --webhook) WEBHOOK="$2"; shift 2 ;;
  40. --branch) BRANCH="$2"; shift 2 ;;
  41. --commit) COMMIT="$2"; shift 2 ;;
  42. --project) PROJECT="$2"; shift 2 ;;
  43. -h|--help)
  44. echo "用法: $0 [选项]"
  45. echo ""
  46. echo "部署模式:"
  47. echo " --env ENV 部署环境: testing | production"
  48. echo " --host HOST 目标服务器地址"
  49. echo " --user USER SSH 用户 (默认: deploy)"
  50. echo " --tag TAG 镜像标签 (默认: latest)"
  51. echo " --ssh-key PATH SSH 私钥路径"
  52. echo " --compose-file FILE docker-compose 文件 (默认: docker-compose.yml)"
  53. echo " --health-timeout SEC 健康检查超时秒数 (默认: 120)"
  54. echo " --no-rollback 禁用失败自动回滚"
  55. echo ""
  56. echo "通知模式 (--notify):"
  57. echo " --status STATUS 部署状态: success | failure"
  58. echo " --webhook URL 企业微信 Webhook URL"
  59. echo " --branch BRANCH Git 分支名"
  60. echo " --commit SHA Git 提交 SHA"
  61. echo " --project NAME 项目名称"
  62. exit 0
  63. ;;
  64. *)
  65. echo "❌ 未知参数: $1"
  66. exit 1
  67. ;;
  68. esac
  69. done
  70. # ==================== 通知函数 ====================
  71. send_notification() {
  72. if [ -z "$WEBHOOK" ] || [ "$WEBHOOK" = "" ]; then
  73. echo "⚠️ 未配置 Webhook URL,跳过通知"
  74. return 0
  75. fi
  76. local emoji="✅"
  77. local status_text="部署成功"
  78. if [ "$STATUS" = "failure" ]; then
  79. emoji="❌"
  80. status_text="部署失败"
  81. fi
  82. local commit_short="${COMMIT:0:8}"
  83. # 企业微信 Markdown 消息格式
  84. local payload
  85. payload=$(cat <<EOF
  86. {
  87. "msgtype": "markdown",
  88. "markdown": {
  89. "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')"
  90. }
  91. }
  92. EOF
  93. )
  94. echo "📤 发送部署通知..."
  95. local response
  96. response=$(curl -s -w "\n%{http_code}" -X POST "$WEBHOOK" \
  97. -H "Content-Type: application/json" \
  98. -d "$payload" 2>/dev/null || echo -e "\n000")
  99. local http_code
  100. http_code=$(echo "$response" | tail -1)
  101. local body
  102. body=$(echo "$response" | head -n -1)
  103. if [ "$http_code" = "200" ]; then
  104. echo "✅ 通知发送成功"
  105. else
  106. echo "⚠️ 通知发送失败 (HTTP ${http_code}): ${body}"
  107. fi
  108. }
  109. # ==================== 健康检查 ====================
  110. wait_for_healthy() {
  111. local host="$1"
  112. local timeout="$2"
  113. local elapsed=0
  114. echo "⏳ 等待服务健康检查通过 (超时: ${timeout}s)..."
  115. while [ $elapsed -lt $timeout ]; do
  116. # 检查 docker compose 服务状态
  117. local unhealthy
  118. unhealthy=$(ssh -i "$SSH_KEY_PATH" -o StrictHostKeyChecking=no "${USER}@${host}" \
  119. "docker compose -f ${COMPOSE_FILE} ps --format '{{.Service}} {{.Health}}' 2>/dev/null | grep -v 'healthy' | grep -v 'running' | head -5" 2>/dev/null || echo "")
  120. if [ -z "$unhealthy" ]; then
  121. echo "✅ 所有服务健康运行 (${elapsed}s)"
  122. return 0
  123. fi
  124. echo " 等待中... (${elapsed}s / ${timeout}s)"
  125. sleep "$HEALTH_INTERVAL"
  126. elapsed=$((elapsed + HEALTH_INTERVAL))
  127. done
  128. echo "❌ 健康检查超时 (${timeout}s)"
  129. echo " 未健康的服务:"
  130. echo " $unhealthy"
  131. return 1
  132. }
  133. # ==================== 回滚 ====================
  134. rollback() {
  135. local host="$1"
  136. echo "🔄 执行回滚..."
  137. ssh -i "$SSH_KEY_PATH" -o StrictHostKeyChecking=no "${USER}@${host}" bash <<ROLLBACK
  138. set -e
  139. cd /opt/water-management 2>/dev/null || cd ~/water-management
  140. echo "回滚到上一个版本..."
  141. # docker compose 会自动保留上一版本镜像
  142. docker compose -f ${COMPOSE_FILE} up -d --force-recreate 2>/dev/null || {
  143. echo "❌ 回滚失败!请手动检查"
  144. exit 1
  145. }
  146. echo "✅ 回滚完成"
  147. ROLLBACK
  148. }
  149. # ==================== 部署 ====================
  150. deploy() {
  151. if [ -z "$ENV" ]; then
  152. echo "❌ 必须指定 --env 参数 (testing|production)"
  153. exit 1
  154. fi
  155. if [ -z "$HOST" ]; then
  156. echo "❌ 必须指定 --host 参数"
  157. exit 1
  158. fi
  159. echo "========================================="
  160. echo " 智慧水务管理系统 - 部署"
  161. echo " 环境: ${ENV}"
  162. echo " 主机: ${HOST}"
  163. echo " 标签: ${TAG}"
  164. echo "========================================="
  165. # 1. 记录当前版本(用于回滚)
  166. echo ""
  167. echo "📋 记录当前版本..."
  168. ssh -i "$SSH_KEY_PATH" -o StrictHostKeyChecking=no "${USER}@${HOST}" bash <<'SAVE_VERSION'
  169. cd /opt/water-management 2>/dev/null || cd ~/water-management
  170. mkdir -p .deploy
  171. docker compose -f docker-compose.yml config --images 2>/dev/null | sort > .deploy/previous_images.txt || true
  172. echo "VERSION=$(date +%Y%m%d%H%M%S)" > .deploy/previous_version.txt
  173. SAVE_VERSION
  174. # 2. 更新环境变量中的镜像标签
  175. echo ""
  176. echo "📝 更新镜像标签为: ${TAG}"
  177. ssh -i "$SSH_KEY_PATH" -o StrictHostKeyChecking=no "${USER}@${HOST}" bash <<UPDATE_TAG
  178. cd /opt/water-management 2>/dev/null || cd ~/water-management
  179. # 更新 .env 中的 IMAGE_TAG
  180. if [ -f .env ]; then
  181. sed -i "s/^IMAGE_TAG=.*/IMAGE_TAG=${TAG}/" .env 2>/dev/null || echo "IMAGE_TAG=${TAG}" >> .env
  182. else
  183. echo "IMAGE_TAG=${TAG}" > .env
  184. fi
  185. echo "IMAGE_TAG=${TAG}"
  186. UPDATE_TAG
  187. # 3. 拉取新镜像
  188. echo ""
  189. echo "📥 拉取新镜像..."
  190. ssh -i "$SSH_KEY_PATH" -o StrictHostKeyChecking=no "${USER}@${HOST}" bash <<PULL
  191. cd /opt/water-management 2>/dev/null || cd ~/water-management
  192. docker compose -f ${COMPOSE_FILE} pull 2>&1 || {
  193. echo "❌ 镜像拉取失败"
  194. exit 1
  195. }
  196. PULL
  197. # 4. 启动服务
  198. echo ""
  199. echo "🚀 启动服务..."
  200. ssh -i "$SSH_KEY_PATH" -o StrictHostKeyChecking=no "${USER}@${HOST}" bash <<UP
  201. cd /opt/water-management 2>/dev/null || cd ~/water-management
  202. docker compose -f ${COMPOSE_FILE} up -d --remove-orphans 2>&1 || {
  203. echo "❌ 服务启动失败"
  204. exit 1
  205. }
  206. UP
  207. # 5. 健康检查
  208. echo ""
  209. if wait_for_healthy "$HOST" "$HEALTH_TIMEOUT"; then
  210. echo ""
  211. echo "========================================="
  212. echo " ✅ 部署成功!"
  213. echo " 环境: ${ENV}"
  214. echo " 标签: ${TAG}"
  215. echo "========================================="
  216. return 0
  217. else
  218. echo ""
  219. echo "========================================="
  220. echo " ❌ 部署失败!"
  221. echo "========================================="
  222. if [ "$ROLLBACK_ON_FAILURE" = true ]; then
  223. rollback "$HOST"
  224. fi
  225. return 1
  226. fi
  227. }
  228. # ==================== 主逻辑 ====================
  229. if [ "$NOTIFY_MODE" = true ]; then
  230. send_notification
  231. else
  232. deploy
  233. fi