Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.
 
 
 

345 строки
8.6 KiB

  1. #!/usr/bin/env bash
  2. set -Eeuo pipefail
  3. # Build selected app images and package them as an incremental update.
  4. # Usage:
  5. # ./build-update.sh admin monitor
  6. # SERVICES="admin monitor" ./build-update.sh
  7. # EMP_ROOT=/path/to/emp IMAGE_TAG=20260602153000 ./build-update.sh emp-admin emp-monitor
  8. #
  9. # Output:
  10. # dist/${DEPLOY_ENV}-update-${IMAGE_TAG}-${services}.tar.gz
  11. SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
  12. DIST_DIR="${DIST_DIR:-$SCRIPT_DIR/dist}"
  13. BUILD_CONTEXT_DIR="$SCRIPT_DIR/.build-context"
  14. DEPLOY_ENV="${DEPLOY_ENV:-emp-test}"
  15. IMAGE_NAMESPACE="${IMAGE_NAMESPACE:-$DEPLOY_ENV}"
  16. IMAGE_TAG="${IMAGE_TAG:-$(date '+%Y%m%d%H%M%S')}"
  17. NPM_REGISTRY="${NPM_REGISTRY:-https://registry.npmjs.org}"
  18. SKIP_BUILD="${SKIP_BUILD:-0}"
  19. COS_UPLOAD="${COS_UPLOAD:-0}"
  20. log() {
  21. echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"
  22. }
  23. die() {
  24. echo "ERROR: $*" >&2
  25. exit 1
  26. }
  27. need_cmd() {
  28. command -v "$1" >/dev/null 2>&1 || die "Missing command: $1"
  29. }
  30. has_emp_root() {
  31. [[ -d "$1/emp_server" && -d "$1/emp_admin" && -d "$1/emp_ws" ]]
  32. }
  33. resolve_root_dir() {
  34. if [[ -n "${EMP_ROOT:-}" ]]; then
  35. local root
  36. root="$(cd "$EMP_ROOT" && pwd)"
  37. has_emp_root "$root" || die "EMP_ROOT is invalid: $EMP_ROOT"
  38. printf '%s\n' "$root"
  39. return
  40. fi
  41. local candidates=(
  42. "$SCRIPT_DIR/../.."
  43. "$SCRIPT_DIR/../../emp"
  44. "$SCRIPT_DIR/.."
  45. "$PWD"
  46. )
  47. local candidate root
  48. for candidate in "${candidates[@]}"; do
  49. if [[ -d "$candidate" ]]; then
  50. root="$(cd "$candidate" && pwd)"
  51. if has_emp_root "$root"; then
  52. printf '%s\n' "$root"
  53. return
  54. fi
  55. fi
  56. done
  57. die "Cannot find EMP root. Set EMP_ROOT=/path/to/emp where emp_server, emp_admin and emp_ws exist."
  58. }
  59. normalize_service() {
  60. case "$1" in
  61. gateway|emp-gateway) echo "gateway" ;;
  62. auth|emp-auth) echo "auth" ;;
  63. monitor|emp-monitor) echo "monitor" ;;
  64. data|emp-data) echo "data" ;;
  65. pdf|emp-pdf) echo "pdf" ;;
  66. ws|emp-ws) echo "ws" ;;
  67. admin|emp-admin) echo "admin" ;;
  68. *) die "Unknown service: $1. Allowed: gateway auth monitor data pdf ws admin" ;;
  69. esac
  70. }
  71. compose_service_name() {
  72. echo "emp-$1"
  73. }
  74. image_name() {
  75. echo "$IMAGE_NAMESPACE/emp-$1"
  76. }
  77. java_module_name() {
  78. case "$1" in
  79. gateway) echo "emp_gateway" ;;
  80. auth) echo "emp_auth" ;;
  81. monitor) echo "emp_monitor" ;;
  82. data) echo "emp_data" ;;
  83. *) return 1 ;;
  84. esac
  85. }
  86. is_java_service() {
  87. case "$1" in
  88. gateway|auth|monitor|data) return 0 ;;
  89. *) return 1 ;;
  90. esac
  91. }
  92. read_requested_services() {
  93. local raw_services=()
  94. if [[ "$#" -gt 0 ]]; then
  95. raw_services=("$@")
  96. elif [[ -n "${SERVICES:-}" ]]; then
  97. # shellcheck disable=SC2206
  98. raw_services=($SERVICES)
  99. else
  100. die "No services specified. Example: ./build-update.sh admin monitor"
  101. fi
  102. local -A seen=()
  103. local service normalized
  104. for service in "${raw_services[@]}"; do
  105. normalized="$(normalize_service "$service")"
  106. if [[ -z "${seen[$normalized]:-}" ]]; then
  107. seen[$normalized]=1
  108. echo "$normalized"
  109. fi
  110. done
  111. }
  112. join_by_comma() {
  113. local IFS=,
  114. echo "$*"
  115. }
  116. run_step() {
  117. local workdir="$1"
  118. shift
  119. (cd "$workdir" && "$@")
  120. }
  121. build_java_images() {
  122. local services=("$@")
  123. [[ "${#services[@]}" -gt 0 ]] || return 0
  124. local server_dir="$ROOT_DIR/emp_server"
  125. local modules=()
  126. local service module jar target_dir
  127. for service in "${services[@]}"; do
  128. modules+=("$(java_module_name "$service")")
  129. done
  130. log "Build backend jars: ${modules[*]}"
  131. run_step "$server_dir" mvn package -DskipTests -B -pl "$(join_by_comma "${modules[@]}")" -am
  132. for service in "${services[@]}"; do
  133. module="$(java_module_name "$service")"
  134. target_dir="$server_dir/$module/target"
  135. jar="$(find "$target_dir" -maxdepth 1 -name '*.jar' ! -name '*original*' ! -name 'app.jar' | head -1)"
  136. [[ -n "$jar" ]] || die "Jar not found for $module"
  137. cp "$jar" "$target_dir/app.jar"
  138. log "Build image: $(image_name "$service"):$IMAGE_TAG"
  139. run_step "$server_dir" docker build \
  140. -f Dockerfile.service \
  141. --build-arg "MODULE=$module" \
  142. -t "$(image_name "$service"):$IMAGE_TAG" \
  143. -t "$(image_name "$service"):latest" \
  144. .
  145. done
  146. }
  147. build_pdf_image() {
  148. local server_dir="$ROOT_DIR/emp_server"
  149. log "Build image: $(image_name pdf):$IMAGE_TAG"
  150. run_step "$server_dir" docker build \
  151. -f emp_pdf/Dockerfile \
  152. -t "$(image_name pdf):$IMAGE_TAG" \
  153. -t "$(image_name pdf):latest" \
  154. emp_pdf
  155. }
  156. build_ws_image() {
  157. log "Build image: $(image_name ws):$IMAGE_TAG"
  158. run_step "$ROOT_DIR" docker build \
  159. -f "$SCRIPT_DIR/dockerfiles/emp-ws.Dockerfile" \
  160. --build-arg "NPM_REGISTRY=$NPM_REGISTRY" \
  161. -t "$(image_name ws):$IMAGE_TAG" \
  162. -t "$(image_name ws):latest" \
  163. .
  164. }
  165. build_admin_image() {
  166. local admin_dir="$ROOT_DIR/emp_admin"
  167. local context_dir="$BUILD_CONTEXT_DIR/emp-admin"
  168. log "Build frontend dist"
  169. run_step "$admin_dir" pnpm install --frozen-lockfile
  170. run_step "$admin_dir" pnpm run build:shunfeng
  171. log "Prepare frontend image context"
  172. rm -rf "$context_dir"
  173. mkdir -p "$context_dir"
  174. cp -R "$admin_dir/dist" "$context_dir/dist"
  175. cp "$SCRIPT_DIR/nginx/admin.conf" "$context_dir/admin.conf"
  176. log "Build image: $(image_name admin):$IMAGE_TAG"
  177. run_step "$context_dir" docker build \
  178. -f "$SCRIPT_DIR/dockerfiles/emp-admin.Dockerfile" \
  179. -t "$(image_name admin):$IMAGE_TAG" \
  180. -t "$(image_name admin):latest" \
  181. .
  182. }
  183. prepare_package() {
  184. local services_slug="$1"
  185. PACKAGE_NAME="${PACKAGE_NAME:-${DEPLOY_ENV}-update-${IMAGE_TAG}-${services_slug}}"
  186. PACKAGE_DIR="$DIST_DIR/$PACKAGE_NAME"
  187. PACKAGE_ARCHIVE="$DIST_DIR/${PACKAGE_NAME}.tar.gz"
  188. rm -rf "$PACKAGE_DIR"
  189. mkdir -p "$PACKAGE_DIR"
  190. cp "$SCRIPT_DIR/apply-update.sh" "$PACKAGE_DIR/apply-update.sh"
  191. chmod +x "$PACKAGE_DIR/apply-update.sh"
  192. local service
  193. for service in "${REQUESTED_SERVICES[@]}"; do
  194. compose_service_name "$service"
  195. done > "$PACKAGE_DIR/services.txt"
  196. {
  197. echo "deploy_env=$DEPLOY_ENV"
  198. echo "image_namespace=$IMAGE_NAMESPACE"
  199. echo "image_tag=$IMAGE_TAG"
  200. echo "services=${REQUESTED_SERVICES[*]}"
  201. echo "compose_services=$(tr '\n' ' ' < "$PACKAGE_DIR/services.txt")"
  202. echo "created_at=$(date '+%Y-%m-%d %H:%M:%S')"
  203. } > "$PACKAGE_DIR/manifest.txt"
  204. }
  205. save_images() {
  206. local images=()
  207. local service image
  208. for service in "${REQUESTED_SERVICES[@]}"; do
  209. image="$(image_name "$service")"
  210. images+=("$image:$IMAGE_TAG" "$image:latest")
  211. done
  212. log "Save images: ${images[*]}"
  213. docker save -o "$PACKAGE_DIR/images.tar" "${images[@]}"
  214. }
  215. archive_package() {
  216. mkdir -p "$DIST_DIR"
  217. rm -f "$PACKAGE_ARCHIVE"
  218. tar -czf "$PACKAGE_ARCHIVE" -C "$DIST_DIR" "$PACKAGE_NAME"
  219. log "Update package created: $PACKAGE_ARCHIVE"
  220. }
  221. publish_package() {
  222. if [[ "$COS_UPLOAD" != "1" ]]; then
  223. return
  224. fi
  225. log "Upload update package to COS"
  226. DEPLOY_ENV="$DEPLOY_ENV" PACKAGE_KIND=update bash "$SCRIPT_DIR/publish-cos.sh" "$PACKAGE_ARCHIVE"
  227. }
  228. need_cmd docker
  229. need_cmd tar
  230. mapfile -t REQUESTED_SERVICES < <(read_requested_services "$@")
  231. [[ "${#REQUESTED_SERVICES[@]}" -gt 0 ]] || die "No services to update."
  232. ROOT_DIR="$(resolve_root_dir)"
  233. SERVICES_SLUG="$(IFS=-; echo "${REQUESTED_SERVICES[*]}")"
  234. log "EMP root: $ROOT_DIR"
  235. log "Deploy root: $SCRIPT_DIR"
  236. log "Deploy env: $DEPLOY_ENV"
  237. log "Update services: ${REQUESTED_SERVICES[*]}"
  238. log "Image tag: $IMAGE_TAG"
  239. if [[ "$SKIP_BUILD" != "1" ]]; then
  240. JAVA_SERVICES=()
  241. NEED_PNPM=0
  242. for service in "${REQUESTED_SERVICES[@]}"; do
  243. if is_java_service "$service"; then
  244. JAVA_SERVICES+=("$service")
  245. fi
  246. if [[ "$service" == "admin" ]]; then
  247. NEED_PNPM=1
  248. fi
  249. done
  250. if [[ "${#JAVA_SERVICES[@]}" -gt 0 ]]; then
  251. need_cmd mvn
  252. fi
  253. if [[ "$NEED_PNPM" == "1" ]]; then
  254. need_cmd pnpm
  255. fi
  256. build_java_images "${JAVA_SERVICES[@]}"
  257. for service in "${REQUESTED_SERVICES[@]}"; do
  258. case "$service" in
  259. pdf) build_pdf_image ;;
  260. ws) build_ws_image ;;
  261. admin) build_admin_image ;;
  262. esac
  263. done
  264. else
  265. log "Skip build, package existing local images only"
  266. fi
  267. prepare_package "$SERVICES_SLUG"
  268. save_images
  269. archive_package
  270. publish_package
  271. if [[ "$COS_UPLOAD" == "1" ]]; then
  272. cat <<EOF
  273. COS upload finished. Use the "Target deploy command" above on the target server.
  274. EOF
  275. else
  276. cat <<EOF
  277. Next steps on target server:
  278. Copy $(basename "$PACKAGE_ARCHIVE") to the target server, then run:
  279. Manual fallback:
  280. mkdir -p /tmp/emp-update
  281. tar -xzf $(basename "$PACKAGE_ARCHIVE") -C /tmp/emp-update --strip-components=1
  282. cd /tmp/emp-update
  283. bash apply-update.sh
  284. If the target runtime uses a compose override, pass all compose files:
  285. COMPOSE_FILES="/home/admin-x99/emp/$DEPLOY_ENV/runtime/docker-compose.yml:/home/admin-x99/emp/$DEPLOY_ENV/runtime/docker-compose.logs.yml" bash apply-update.sh
  286. EOF
  287. fi