25'ten fazla konu seçemezsiniz Konular bir harf veya rakamla başlamalı, kısa çizgiler ('-') içerebilir ve en fazla 35 karakter uzunluğunda olabilir.
 
 
 

403 satır
10 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. # emp-test / emp-uat 继续使用带环境命名空间的镜像名;shunfeng 为兼容
  21. # 现网 emp-gateway:latest / emp-auth:latest 等平铺名称,不增加命名空间。
  22. if [[ "$DEPLOY_ENV" == "emp-shunfeng" ]]; then
  23. DEFAULT_IMAGE_NAME_PREFIX=""
  24. else
  25. DEFAULT_IMAGE_NAME_PREFIX="$IMAGE_NAMESPACE/"
  26. fi
  27. IMAGE_NAME_PREFIX="${IMAGE_NAME_PREFIX-$DEFAULT_IMAGE_NAME_PREFIX}"
  28. log() {
  29. echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"
  30. }
  31. die() {
  32. echo "ERROR: $*" >&2
  33. exit 1
  34. }
  35. need_cmd() {
  36. command -v "$1" >/dev/null 2>&1 || die "Missing command: $1"
  37. }
  38. has_emp_root() {
  39. local root="$1"
  40. local service
  41. [[ -d "$root/emp_server" ]] || return 1
  42. for service in "${REQUESTED_SERVICES[@]}"; do
  43. case "$service" in
  44. admin) [[ -d "$root/emp_admin" ]] || return 1 ;;
  45. ws) [[ -d "$root/emp_ws" ]] || return 1 ;;
  46. esac
  47. done
  48. }
  49. resolve_root_dir() {
  50. if [[ -n "${EMP_ROOT:-}" ]]; then
  51. local root
  52. root="$(cd "$EMP_ROOT" && pwd)"
  53. has_emp_root "$root" || die "EMP_ROOT is invalid: $EMP_ROOT"
  54. printf '%s\n' "$root"
  55. return
  56. fi
  57. local candidates=(
  58. "$SCRIPT_DIR/../.."
  59. "$SCRIPT_DIR/../../emp"
  60. "$SCRIPT_DIR/.."
  61. "$PWD"
  62. )
  63. local candidate root
  64. for candidate in "${candidates[@]}"; do
  65. if [[ -d "$candidate" ]]; then
  66. root="$(cd "$candidate" && pwd)"
  67. if has_emp_root "$root"; then
  68. printf '%s\n' "$root"
  69. return
  70. fi
  71. fi
  72. done
  73. die "Cannot find EMP root for the selected services. Set EMP_ROOT=/path/to/emp."
  74. }
  75. normalize_service() {
  76. case "$1" in
  77. gateway|emp-gateway) echo "gateway" ;;
  78. auth|emp-auth) echo "auth" ;;
  79. monitor|emp-monitor) echo "monitor" ;;
  80. data|emp-data) echo "data" ;;
  81. pdf|emp-pdf) echo "pdf" ;;
  82. ws|emp-ws) echo "ws" ;;
  83. admin|emp-admin) echo "admin" ;;
  84. *) die "Unknown service: $1. Allowed: gateway auth monitor data pdf ws admin" ;;
  85. esac
  86. }
  87. compose_service_name() {
  88. echo "emp-$1"
  89. }
  90. image_name() {
  91. echo "${IMAGE_NAME_PREFIX}emp-$1"
  92. }
  93. java_module_name() {
  94. case "$1" in
  95. gateway) echo "emp_gateway" ;;
  96. auth) echo "emp_auth" ;;
  97. monitor) echo "emp_monitor" ;;
  98. data) echo "emp_data" ;;
  99. *) return 1 ;;
  100. esac
  101. }
  102. is_java_service() {
  103. case "$1" in
  104. gateway|auth|monitor|data) return 0 ;;
  105. *) return 1 ;;
  106. esac
  107. }
  108. read_requested_services() {
  109. local raw_services=()
  110. if [[ "$#" -gt 0 ]]; then
  111. raw_services=("$@")
  112. elif [[ -n "${SERVICES:-}" ]]; then
  113. # shellcheck disable=SC2206
  114. raw_services=($SERVICES)
  115. else
  116. die "No services specified. Example: ./build-update.sh admin monitor"
  117. fi
  118. local -A seen=()
  119. local service normalized
  120. for service in "${raw_services[@]}"; do
  121. normalized="$(normalize_service "$service")"
  122. if [[ -z "${seen[$normalized]:-}" ]]; then
  123. seen[$normalized]=1
  124. echo "$normalized"
  125. fi
  126. done
  127. }
  128. join_by_comma() {
  129. local IFS=,
  130. echo "$*"
  131. }
  132. run_step() {
  133. local workdir="$1"
  134. shift
  135. (cd "$workdir" && "$@")
  136. }
  137. build_java_images() {
  138. local services=("$@")
  139. [[ "${#services[@]}" -gt 0 ]] || return 0
  140. local server_dir="$ROOT_DIR/emp_server"
  141. local modules=()
  142. local service module jar target_dir
  143. for service in "${services[@]}"; do
  144. modules+=("$(java_module_name "$service")")
  145. done
  146. log "Build backend jars: ${modules[*]}"
  147. run_step "$server_dir" mvn package -DskipTests -B -pl "$(join_by_comma "${modules[@]}")" -am
  148. for service in "${services[@]}"; do
  149. module="$(java_module_name "$service")"
  150. target_dir="$server_dir/$module/target"
  151. jar="$(find "$target_dir" -maxdepth 1 -name '*.jar' ! -name '*original*' ! -name 'app.jar' | head -1)"
  152. [[ -n "$jar" ]] || die "Jar not found for $module"
  153. cp "$jar" "$target_dir/app.jar"
  154. log "Build image: $(image_name "$service"):$IMAGE_TAG"
  155. run_step "$server_dir" docker build \
  156. -f Dockerfile.service \
  157. --build-arg "MODULE=$module" \
  158. -t "$(image_name "$service"):$IMAGE_TAG" \
  159. -t "$(image_name "$service"):latest" \
  160. .
  161. done
  162. }
  163. build_pdf_image() {
  164. local server_dir="$ROOT_DIR/emp_server"
  165. log "Build image: $(image_name pdf):$IMAGE_TAG"
  166. run_step "$server_dir" docker build \
  167. -f emp_pdf/Dockerfile \
  168. -t "$(image_name pdf):$IMAGE_TAG" \
  169. -t "$(image_name pdf):latest" \
  170. emp_pdf
  171. }
  172. build_ws_image() {
  173. log "Build image: $(image_name ws):$IMAGE_TAG"
  174. run_step "$ROOT_DIR" docker build \
  175. -f "$SCRIPT_DIR/dockerfiles/emp-ws.Dockerfile" \
  176. --build-arg "NPM_REGISTRY=$NPM_REGISTRY" \
  177. -t "$(image_name ws):$IMAGE_TAG" \
  178. -t "$(image_name ws):latest" \
  179. .
  180. }
  181. build_admin_image() {
  182. local admin_dir="$ROOT_DIR/emp_admin"
  183. local context_dir="$BUILD_CONTEXT_DIR/emp-admin"
  184. local nginx_conf="${ADMIN_NGINX_CONF:-$SCRIPT_DIR/nginx/admin.conf}"
  185. if [[ "$DEPLOY_ENV" == "emp-shunfeng" && -z "${ADMIN_NGINX_CONF:-}" ]]; then
  186. nginx_conf="$admin_dir/nginx.conf"
  187. fi
  188. [[ -f "$nginx_conf" ]] || die "Admin Nginx config not found: $nginx_conf"
  189. log "Build frontend dist"
  190. run_step "$admin_dir" pnpm install --frozen-lockfile
  191. run_step "$admin_dir" pnpm run build:shunfeng
  192. log "Prepare frontend image context"
  193. rm -rf "$context_dir"
  194. mkdir -p "$context_dir"
  195. cp -R "$admin_dir/dist" "$context_dir/dist"
  196. cp "$nginx_conf" "$context_dir/admin.conf"
  197. log "Build image: $(image_name admin):$IMAGE_TAG"
  198. run_step "$context_dir" docker build \
  199. -f "$SCRIPT_DIR/dockerfiles/emp-admin.Dockerfile" \
  200. -t "$(image_name admin):$IMAGE_TAG" \
  201. -t "$(image_name admin):latest" \
  202. .
  203. }
  204. prepare_package() {
  205. local services_slug="$1"
  206. local profile_dir="${PROFILE_DIR:-$SCRIPT_DIR/profiles/$DEPLOY_ENV}"
  207. PACKAGE_NAME="${PACKAGE_NAME:-${DEPLOY_ENV}-update-${IMAGE_TAG}-${services_slug}}"
  208. PACKAGE_DIR="$DIST_DIR/$PACKAGE_NAME"
  209. PACKAGE_ARCHIVE="$DIST_DIR/${PACKAGE_NAME}.tar.gz"
  210. rm -rf "$PACKAGE_DIR"
  211. mkdir -p "$PACKAGE_DIR"
  212. cp "$SCRIPT_DIR/apply-update.sh" "$PACKAGE_DIR/apply-update.sh"
  213. if [[ -d "$profile_dir/package-files" ]]; then
  214. cp -R "$profile_dir/package-files/." "$PACKAGE_DIR/"
  215. fi
  216. find "$PACKAGE_DIR" -maxdepth 1 -type f -name '*.sh' -exec chmod +x {} +
  217. if [[ -f "$profile_dir/update-env.defaults" ]]; then
  218. cp "$profile_dir/update-env.defaults" "$PACKAGE_DIR/update-env.defaults"
  219. fi
  220. local service
  221. for service in "${REQUESTED_SERVICES[@]}"; do
  222. compose_service_name "$service"
  223. done > "$PACKAGE_DIR/services.txt"
  224. {
  225. echo "deploy_env=$DEPLOY_ENV"
  226. echo "image_namespace=$IMAGE_NAMESPACE"
  227. echo "image_name_prefix=$IMAGE_NAME_PREFIX"
  228. echo "image_tag=$IMAGE_TAG"
  229. echo "services=${REQUESTED_SERVICES[*]}"
  230. echo "compose_services=$(tr '\n' ' ' < "$PACKAGE_DIR/services.txt")"
  231. echo "created_at=$(date '+%Y-%m-%d %H:%M:%S')"
  232. } > "$PACKAGE_DIR/manifest.txt"
  233. }
  234. save_images() {
  235. local images=()
  236. local service image
  237. for service in "${REQUESTED_SERVICES[@]}"; do
  238. image="$(image_name "$service")"
  239. images+=("$image:$IMAGE_TAG" "$image:latest")
  240. done
  241. log "Save images: ${images[*]}"
  242. docker save -o "$PACKAGE_DIR/images.tar" "${images[@]}"
  243. }
  244. archive_package() {
  245. mkdir -p "$DIST_DIR"
  246. rm -f "$PACKAGE_ARCHIVE"
  247. tar -czf "$PACKAGE_ARCHIVE" -C "$DIST_DIR" "$PACKAGE_NAME"
  248. log "Update package created: $PACKAGE_ARCHIVE"
  249. }
  250. publish_package() {
  251. if [[ "$COS_UPLOAD" != "1" ]]; then
  252. return
  253. fi
  254. log "Upload update package to COS"
  255. DEPLOY_ENV="$DEPLOY_ENV" PACKAGE_KIND=update bash "$SCRIPT_DIR/publish-cos.sh" "$PACKAGE_ARCHIVE"
  256. }
  257. need_cmd docker
  258. need_cmd tar
  259. mapfile -t REQUESTED_SERVICES < <(read_requested_services "$@")
  260. [[ "${#REQUESTED_SERVICES[@]}" -gt 0 ]] || die "No services to update."
  261. if [[ "$DEPLOY_ENV" == "emp-shunfeng" ]]; then
  262. for service in "${REQUESTED_SERVICES[@]}"; do
  263. case "$service" in
  264. pdf|ws) die "emp-shunfeng only supports admin, gateway, auth, monitor and data" ;;
  265. esac
  266. done
  267. fi
  268. ROOT_DIR="$(resolve_root_dir)"
  269. SERVICES_SLUG="$(IFS=-; echo "${REQUESTED_SERVICES[*]}")"
  270. log "EMP root: $ROOT_DIR"
  271. log "Deploy root: $SCRIPT_DIR"
  272. log "Deploy env: $DEPLOY_ENV"
  273. log "Update services: ${REQUESTED_SERVICES[*]}"
  274. log "Image tag: $IMAGE_TAG"
  275. if [[ "$SKIP_BUILD" != "1" ]]; then
  276. JAVA_SERVICES=()
  277. NEED_PNPM=0
  278. for service in "${REQUESTED_SERVICES[@]}"; do
  279. if is_java_service "$service"; then
  280. JAVA_SERVICES+=("$service")
  281. fi
  282. if [[ "$service" == "admin" ]]; then
  283. NEED_PNPM=1
  284. fi
  285. done
  286. if [[ "${#JAVA_SERVICES[@]}" -gt 0 ]]; then
  287. need_cmd mvn
  288. fi
  289. if [[ "$NEED_PNPM" == "1" ]]; then
  290. need_cmd pnpm
  291. fi
  292. build_java_images "${JAVA_SERVICES[@]}"
  293. for service in "${REQUESTED_SERVICES[@]}"; do
  294. case "$service" in
  295. pdf) build_pdf_image ;;
  296. ws) build_ws_image ;;
  297. admin) build_admin_image ;;
  298. esac
  299. done
  300. else
  301. log "Skip build, package existing local images only"
  302. fi
  303. prepare_package "$SERVICES_SLUG"
  304. save_images
  305. archive_package
  306. publish_package
  307. if [[ "$COS_UPLOAD" == "1" ]]; then
  308. cat <<EOF
  309. COS upload finished. Use the "Target deploy command" above on the target server.
  310. EOF
  311. else
  312. if [[ "$DEPLOY_ENV" == "emp-shunfeng" ]]; then
  313. cat <<EOF
  314. Next steps on the shunfeng master server:
  315. Copy $(basename "$PACKAGE_ARCHIVE") to the master server, then run:
  316. mkdir -p /tmp/emp-shunfeng-update
  317. tar -xzf $(basename "$PACKAGE_ARCHIVE") -C /tmp/emp-shunfeng-update --strip-components=1
  318. cd /tmp/emp-shunfeng-update
  319. DEPLOY_ENV=emp-shunfeng DEPLOY_HOME=/home/emp-shunfeng bash apply-update.sh
  320. EOF
  321. exit 0
  322. fi
  323. cat <<EOF
  324. Next steps on target server:
  325. Copy $(basename "$PACKAGE_ARCHIVE") to the target server, then run:
  326. Manual fallback:
  327. mkdir -p /tmp/emp-update
  328. tar -xzf $(basename "$PACKAGE_ARCHIVE") -C /tmp/emp-update --strip-components=1
  329. cd /tmp/emp-update
  330. bash apply-update.sh
  331. If the target runtime uses a compose override, pass all compose files:
  332. 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
  333. EOF
  334. fi