您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
 
 
 

218 行
5.6 KiB

  1. #!/usr/bin/env bash
  2. set -Eeuo pipefail
  3. # Apply an incremental image update on the target runtime server.
  4. # Usage:
  5. # bash apply-update.sh
  6. # bash apply-update.sh emp-admin emp-monitor
  7. # DEPLOY_ENV=emp-uat ENV_FILE=.env COMPOSE_FILE=docker-compose.yml bash apply-update.sh
  8. # COMPOSE_FILES="/path/docker-compose.yml:/path/docker-compose.logs.yml" bash apply-update.sh
  9. SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
  10. cd "$SCRIPT_DIR"
  11. DEPLOY_ENV="${DEPLOY_ENV:-emp-test}"
  12. PROJECT_NAME="${PROJECT_NAME:-$DEPLOY_ENV}"
  13. DEPLOY_HOME="${DEPLOY_HOME:-/home/admin-x99/$DEPLOY_ENV}"
  14. ENV_FILE="${ENV_FILE:-.env}"
  15. COMPOSE_FILE="${COMPOSE_FILE:-docker-compose.yml}"
  16. COMPOSE_FILES="${COMPOSE_FILES:-}"
  17. IMAGE_TAR="${IMAGE_TAR:-images.tar}"
  18. SERVICE_FILE="${SERVICE_FILE:-services.txt}"
  19. log() {
  20. echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"
  21. }
  22. die() {
  23. echo "ERROR: $*" >&2
  24. exit 1
  25. }
  26. need_cmd() {
  27. command -v "$1" >/dev/null 2>&1 || die "Missing command: $1"
  28. }
  29. normalize_service() {
  30. case "$1" in
  31. gateway|emp-gateway) echo "emp-gateway" ;;
  32. auth|emp-auth) echo "emp-auth" ;;
  33. monitor|emp-monitor) echo "emp-monitor" ;;
  34. data|emp-data) echo "emp-data" ;;
  35. pdf|emp-pdf) echo "emp-pdf" ;;
  36. ws|emp-ws) echo "emp-ws" ;;
  37. admin|emp-admin) echo "emp-admin" ;;
  38. *) die "Unknown service: $1. Allowed: gateway auth monitor data pdf ws admin" ;;
  39. esac
  40. }
  41. resolve_compose_cmd() {
  42. if docker compose version >/dev/null 2>&1; then
  43. echo "docker compose"
  44. elif command -v docker-compose >/dev/null 2>&1; then
  45. echo "docker-compose"
  46. else
  47. die "Missing docker compose"
  48. fi
  49. }
  50. resolve_runtime_file() {
  51. local requested="$1"
  52. local default_name="$2"
  53. if [[ -f "$requested" ]]; then
  54. echo "$requested"
  55. return
  56. fi
  57. if [[ "$requested" != "$default_name" ]]; then
  58. echo "$requested"
  59. return
  60. fi
  61. local candidates=(
  62. "../runtime/$default_name"
  63. "$DEPLOY_HOME/runtime/$default_name"
  64. "$DEPLOY_HOME/$default_name"
  65. "/opt/$DEPLOY_ENV/$default_name"
  66. )
  67. local candidate
  68. for candidate in "${candidates[@]}"; do
  69. if [[ -f "$candidate" ]]; then
  70. echo "$candidate"
  71. return
  72. fi
  73. done
  74. echo "$requested"
  75. }
  76. resolve_compose_files() {
  77. local raw_files=()
  78. local file resolved
  79. if [[ -n "$COMPOSE_FILES" ]]; then
  80. IFS=':' read -r -a raw_files <<< "$COMPOSE_FILES"
  81. else
  82. raw_files=("$COMPOSE_FILE")
  83. fi
  84. COMPOSE_ARGS=()
  85. RESOLVED_COMPOSE_FILES=()
  86. for file in "${raw_files[@]}"; do
  87. [[ -n "$file" ]] || continue
  88. resolved="$(resolve_runtime_file "$file" "docker-compose.yml")"
  89. [[ -f "$resolved" ]] || die "Missing compose file: $resolved"
  90. RESOLVED_COMPOSE_FILES+=("$resolved")
  91. COMPOSE_ARGS+=("-f" "$resolved")
  92. done
  93. [[ "${#COMPOSE_ARGS[@]}" -gt 0 ]] || die "No compose files resolved."
  94. }
  95. read_services() {
  96. local raw_services=()
  97. if [[ "$#" -gt 0 ]]; then
  98. raw_services=("$@")
  99. elif [[ -f "$SERVICE_FILE" ]]; then
  100. mapfile -t raw_services < "$SERVICE_FILE"
  101. else
  102. die "No services specified. Pass services as args or provide $SERVICE_FILE."
  103. fi
  104. local service
  105. for service in "${raw_services[@]}"; do
  106. [[ -n "$service" ]] || continue
  107. normalize_service "$service"
  108. done
  109. }
  110. contains_service() {
  111. local target="$1"
  112. shift
  113. local service
  114. for service in "$@"; do
  115. if [[ "$service" == "$target" ]]; then
  116. return 0
  117. fi
  118. done
  119. return 1
  120. }
  121. escape_sed_replacement() {
  122. local value="$1"
  123. value="${value//\\/\\\\}"
  124. value="${value//&/\\&}"
  125. value="${value//|/\\|}"
  126. printf '%s\n' "$value"
  127. }
  128. apply_runtime_env_defaults() {
  129. local defaults_file="$SCRIPT_DIR/update-env.defaults"
  130. [[ -f "$defaults_file" ]] || return 0
  131. local backup_file line key value escaped
  132. backup_file="${ENV_FILE}.bak-$(date '+%Y%m%d%H%M%S')"
  133. cp "$ENV_FILE" "$backup_file"
  134. log "Apply runtime env defaults: $defaults_file"
  135. log "Runtime env backup: $backup_file"
  136. while IFS= read -r line || [[ -n "$line" ]]; do
  137. [[ -n "$line" ]] || continue
  138. [[ "$line" == \#* ]] && continue
  139. [[ "$line" == *"="* ]] || continue
  140. key="${line%%=*}"
  141. value="${line#*=}"
  142. [[ "$key" =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]] || die "Invalid env key in $defaults_file: $key"
  143. escaped="$(escape_sed_replacement "$value")"
  144. if grep -q "^${key}=" "$ENV_FILE"; then
  145. sed -i "s|^${key}=.*|${key}=${escaped}|" "$ENV_FILE"
  146. else
  147. printf '\n%s=%s\n' "$key" "$value" >> "$ENV_FILE"
  148. fi
  149. done < "$defaults_file"
  150. }
  151. need_cmd docker
  152. ENV_FILE="$(resolve_runtime_file "$ENV_FILE" ".env")"
  153. resolve_compose_files
  154. [[ -f "$ENV_FILE" ]] || die "Missing env file: $ENV_FILE"
  155. [[ -f "$IMAGE_TAR" ]] || die "Missing image tar: $IMAGE_TAR"
  156. mapfile -t UPDATE_SERVICES < <(read_services "$@")
  157. [[ "${#UPDATE_SERVICES[@]}" -gt 0 ]] || die "No services to update."
  158. COMPOSE_CMD="$(resolve_compose_cmd)"
  159. apply_runtime_env_defaults
  160. log "Compose files: ${RESOLVED_COMPOSE_FILES[*]}"
  161. log "Load images: $IMAGE_TAR"
  162. docker load -i "$IMAGE_TAR"
  163. log "Recreate services: ${UPDATE_SERVICES[*]}"
  164. # shellcheck disable=SC2086
  165. $COMPOSE_CMD \
  166. --env-file "$ENV_FILE" \
  167. "${COMPOSE_ARGS[@]}" \
  168. -p "$PROJECT_NAME" \
  169. up -d --no-deps --force-recreate "${UPDATE_SERVICES[@]}"
  170. if ! contains_service "emp-admin" "${UPDATE_SERVICES[@]}" \
  171. && { contains_service "emp-ws" "${UPDATE_SERVICES[@]}" || contains_service "emp-gateway" "${UPDATE_SERVICES[@]}"; }; then
  172. log "Restart emp-admin to refresh Nginx upstream DNS"
  173. # shellcheck disable=SC2086
  174. $COMPOSE_CMD \
  175. --env-file "$ENV_FILE" \
  176. "${COMPOSE_ARGS[@]}" \
  177. -p "$PROJECT_NAME" \
  178. restart emp-admin
  179. fi
  180. log "Current service status"
  181. # shellcheck disable=SC2086
  182. $COMPOSE_CMD \
  183. --env-file "$ENV_FILE" \
  184. "${COMPOSE_ARGS[@]}" \
  185. -p "$PROJECT_NAME" \
  186. ps "${UPDATE_SERVICES[@]}"