From 34981c30a78e8bbd7791131059a9210f9928b62c Mon Sep 17 00:00:00 2001
From: 刘光辉 <347230014@qq.com>
Date: 星期四, 17 九月 2026 09:24:09 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/master' into master

---
 docker/preflight.sh |  354 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 354 insertions(+), 0 deletions(-)

diff --git a/docker/preflight.sh b/docker/preflight.sh
new file mode 100644
index 0000000..cabcaa0
--- /dev/null
+++ b/docker/preflight.sh
@@ -0,0 +1,354 @@
+#!/usr/bin/env bash
+# 鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+# JNPF 鍏ㄦ爤閮ㄧ讲鑷妇鏍¢獙锛圔acklog C1锛�
+#   make up / make up-no-pull 鐨勫己鍒跺墠缃紙SKIP_PREFLIGHT=1 閫冪敓锛夛紝涔熷彲鐙珛杩愯銆�
+#   鐩爣锛氭妸"绌哄簱 PG 鍗� healthy銆佸紩鎿�/涓氬姟鍏ㄧ嚎杩炰笉涓�"鐨勯潤榛樺け璐ユ嫤鍦�
+#   compose up 涔嬪墠锛岀己椤圭洿鎺ョ粰鍑轰慨澶嶆寚寮曪紙migration/install.sh銆両NSTALL.md锛夈��
+#
+# 鏍¢獙鍒嗕笁灞傦細
+#   纭墠缃紙澶辫触绔嬪嵆閫�鍑猴級  锛歞ocker CLI / daemon 鍙揪 / compose v2 鎻掍欢 / 缂栨帓鏂囦欢瀛樺湪
+#   鑱氬悎妫�鏌ワ紙璺戝畬缁熶竴姹囨�伙級锛�.env 蹇呭~閿紙浠� compose 鐨� ${VAR:?} 鍔ㄦ�佹彁鍙栵級銆�
+#     瀹㈡埛鍧愭爣锛圕USTOMER_DB_HOST/PORT 蹇呭~ + 瀹炶繛鎺㈡祴 + 涓� localdb profile 閰嶅锛夈��
+#     postgres_pgdata 鍗枫�丳G 瀹瑰櫒鍙捣涓旀帴鍙楄繛鎺ャ�佷笁搴撹〃鏁� 鈮� 鍩哄噯銆�
+#     绉嶅瓙鏁版嵁锛坖npf_init 鏈� admin锛夈�佸叡浜厤缃枃浠跺氨浣嶏紙琛ヤ竵 #8锛夈�佷笟鍔¤鑹查綈
+#   璀﹀憡锛堜笉鎷︽埅锛夛細Docker 鍙敤鍐呭瓨 <12GB锛圠024锛夈�佸涓� hosts 缂� cx-infra銆�
+#
+# 涓ょ搴撳舰鎬侊紝妫�鏌ラ」鎸� CUSTOMER_DB_HOST 鑷姩鍒嗘祦锛�2026-08-07锛夛細
+#   鏍堝唴 PG锛�=cx-postgres锛屼氦浠�/姝e紡锛夛細鍏ㄥ璺戯紝鍚嵎/瀹瑰櫒/涓夊簱/绉嶅瓙/瑙掕壊
+#   杩滅瀹㈡埛搴擄紙=CVM 涓婄殑瀹㈡埛瀹炰緥锛屽紑鍙戞満/娴嬭瘯鐜锛夛細3-5 涓庤鑹叉鏌ヨ烦杩団�斺�旈偅杈圭殑
+#     鍗枫�乻chema銆佺瀛愩�佽鑹茬敱璇� PG 瀹炰緥鑷韩鐨勮繍缁磋礋璐o紙docs/db-connection-pool-guide.md锛夛紝
+#     鏈満鏃㈣捣涓嶄簡閭d釜瀹瑰櫒涔� docker exec 涓嶈繘鍘伙紱鏈満渚у彧楠屽潗鏍囧彲杈句笌鍏变韩閰嶇疆鏂囦欢
+#
+# 鐢ㄦ硶锛�
+#   ./docker/preflight.sh          # 浠撳簱鏍规墽琛岋紙绂荤嚎浜や粯鍖呭唴鍚岃矾寰勶紝鍖呮牴鎵ц锛�
+#
+# 鍙傛暟锛堢幆澧冨彉閲忥紝榛樿=鐜板満鏍囧噯鍊硷紱鏀瑰悕鍙仛闅旂娴嬭瘯锛屼笉鍔ㄧ湡搴擄級锛�
+#   CONTAINER     PG 瀹瑰櫒鍚�                榛樿 cx-postgres
+#   VOLUME_NAME   PG external 鏁版嵁鍗峰悕     榛樿 postgres_pgdata
+#   ENV_FILE      鍑嵁鏂囦欢                 榛樿 .env
+#   COMPOSE_FILE_PATH  缂栨帓鏂囦欢            榛樿 docker-compose.yml
+#   DB_INIT / DB_FLOW / DB_XXLJOB  涓夊簱鍚�
+#   PG_WAIT_SECS  绛� PG 鎺ュ彈杩炴帴鐨勪笂闄愮鏁� 榛樿 180锛堝惈澶у嵎 crash recovery 绐楀彛锛�
+# 鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+set -euo pipefail
+trap 'exit 130' INT
+trap 'exit 143' TERM
+
+CONTAINER="${CONTAINER:-cx-postgres}"
+VOLUME_NAME="${VOLUME_NAME:-postgres_pgdata}"
+ENV_FILE="${ENV_FILE:-.env}"
+COMPOSE_FILE_PATH="${COMPOSE_FILE_PATH:-docker-compose.yml}"
+DB_INIT="${DB_INIT:-jnpf_init}"
+DB_FLOW="${DB_FLOW:-jnpf_flow}"
+DB_XXLJOB="${DB_XXLJOB:-jnpf_xxljob}"
+PG_WAIT_SECS="${PG_WAIT_SECS:-180}"
+
+# 涓夊簱琛ㄦ暟鍩哄噯锛堜笅闄愶紝鍙厑璁� 鈮ワ紝涓氬姟鎵╄〃涓嶈鎶ワ紱install.sh 鐢ㄧ簿纭尮閰嶆牳瀵癸紝鏈鍒绘剰淇濆畧锛�
+# 娉細73鈫�74 鍙负 20260724 鏂板 audit_events +1锛涗笌 install.sh 鐜拌銆屽畼鏂�293+鑷缓84=377銆嶇殑
+# 绮剧‘鍩哄噯瀛樺湪鏃㈡湁 10 寮犳紓绉伙紙鏃╀簬鏈鏀瑰姩锛屾湭鍦ㄦ湰娆¤寖鍥村唴涓�骞惰拷骞筹紝閬垮厤璇敼鏃犲叧鍩哄噯锛�
+BASE_INIT=367   # 瀹樻柟 293 + 鑷缓 74
+BASE_FLOW=70
+BASE_XXLJOB=11
+
+log()  { printf '\033[1;34m[preflight]\033[0m %s\n' "$*"; }
+warnp(){ printf '\033[1;33m[preflight][warn]\033[0m %s\n' "$*"; }
+die()  { printf '\033[1;31m[preflight][err]\033[0m %s\n' "$*" >&2; exit 1; }
+
+# 淇鎸囧紩鐨勮矾寰勬寜杩愯浣嶇疆鑷�傚簲锛氱绾夸氦浠樺寘鏍规湁 migration/锛屼粨搴撴牴鍦� docker/postgres/ 涓�
+if [ -f migration/install.sh ]; then
+  MIG_DIR="migration"
+else
+  MIG_DIR="docker/postgres/migration"
+fi
+
+# 鈹�鈹� 鑱氬悎澶辫触鏈哄埗锛氬崟椤瑰け璐ヤ笉涓柇锛岃窇瀹岀粺涓�姹囨�伙紙鍓嶇疆澶辫触鐨勯」鏍囪璺宠繃锛� 鈹�鈹�鈹�鈹�鈹�鈹�
+FAILS=()
+WARNS=()
+ok()   { printf '  \033[1;32m鉁揬033[0m %s\n' "$1"; }
+bad()  { printf '  \033[1;31m鉁梊033[0m %s\n' "$1"; FAILS+=("$1"$'\n'"      淇锛�$2"); }
+warn2(){ printf '  \033[1;33m!\033[0m %s\n' "$1"; WARNS+=("$1 鈥斺�� $2"); }
+# $2 鍙�夛細璺宠繃鍘熷洜鍚庣紑锛岄粯璁ゃ�屽墠缃け璐ャ�嶏紙澶栭儴搴撴ā寮忕瓑涓诲姩璺宠繃鍦烘櫙浼犺嚜宸辩殑鍘熷洜锛�
+skip() { printf '  \033[1;90m-\033[0m %s锛�%s锛岃烦杩囷級\n' "$1" "${2:-鍓嶇疆澶辫触}"; }
+
+psql_exec() { docker exec "$CONTAINER" psql -U postgres "$@"; }
+get_env() { { grep -E "^$1=" "$ENV_FILE" 2>/dev/null | tail -1 | cut -d= -f2-; } || true; }
+
+CDH=""; CDP=""
+
+# 鈹�鈹� 0. 纭墠缃細docker / compose 鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+log "===== 0/6 docker 鐜 ====="
+command -v docker >/dev/null 2>&1 \
+  || die "鏈壘鍒� docker 鍛戒护鈥斺�斿厛瀹夎 Docker Engine锛堝惈 compose v2 鎻掍欢锛�"
+docker info >/dev/null 2>&1 \
+  || die "docker daemon 涓嶅彲杈锯�斺�旂‘璁� Docker/OrbStack 宸插惎鍔紝涓斿綋鍓嶇敤鎴锋湁鏉冮檺锛坉ocker info 鎶ラ敊鍐呭涓哄噯锛�"
+docker compose version >/dev/null 2>&1 \
+  || die "docker compose v2 鎻掍欢涓嶅彲鐢ㄢ�斺�旈渶瑕� Docker Compose V2锛坉ocker compose version 楠岃瘉锛�"
+ok "docker CLI / daemon / compose v2 灏辩华"
+
+# 缂栨帓鏂囦欢瀛樺湪鎬ф彁鍓嶅埌姝ゅ纭墠缃鏌ワ紙鍘熸斁鍦ㄤ笅鏂�"1. .env 蹇呭~閿�"锛屼絾涓嬮潰鑻ュ共
+# 妫�鏌ユ洿鏃╁氨鐢� awk 璇诲彇璇ユ枃浠垛�斺�攕et -e 涓� awk 閬囨枃浠朵笉瀛樺湪浼氫互 awk 鑷韩閫�鍑虹爜瑁搁��锛岀粫杩囨墍鏈�
+# die() 鎻愮ず锛涙彁鍒版渶鍓嶉潰纭繚浠讳綍璇诲彇 COMPOSE_FILE_PATH 鐨勫垎鏀箣鍓嶅凡淇濊瘉鏂囦欢瀛樺湪锛�
+[ -f "$COMPOSE_FILE_PATH" ] \
+  || die "缂虹紪鎺掓枃浠� ${COMPOSE_FILE_PATH}鈥斺�旇鍦ㄤ粨搴撴牴/绂荤嚎浜や粯鍖呮牴鐩綍鎵ц"
+
+# 鍐呭瓨绾㈢嚎锛圠024锛氬叏鏍� 16 瀹瑰櫒 <12GB 浼� OOM 杩為攣锛歅G crash 寰幆 + gRPC 瓒呮椂椋庢毚锛�
+# 鍥涜垗浜斿叆鍒� GiB锛歏M 鍚嶄箟 12GB 浼氳鍐呮牳淇濈暀鍚冩帀闆跺ご锛�12288MiB 瀹炴姤 ~12.2GiB鈫掓暣闄ゅ彉 11 璇姤锛�
+MEM_TOTAL="$(docker info --format '{{.MemTotal}}' 2>/dev/null || echo 0)"
+MEM_GIB=$(( (MEM_TOTAL + 512*1024*1024) / 1024 / 1024 / 1024 ))
+if [ "$MEM_TOTAL" -gt 0 ] && [ "$MEM_GIB" -lt 12 ]; then
+  warn2 "Docker 鍙敤鍐呭瓨浠� ${MEM_GIB}GB锛�<12GB 绾㈢嚎锛�" "鍏ㄦ爤闇� 鈮�12GB锛歀inux 鍔犵墿鐞嗗唴瀛�/鍑忓鍣紱OrbStack 鐢� orb config set memory_mib 12288锛圠024锛�"
+else
+  ok "Docker 鍙敤鍐呭瓨 ${MEM_GIB}GB锛堚墺12GB锛�"
+fi
+
+# cx-infra 瀹夸富瑙f瀽锛圓piDomain 鍙屾秷璐规柟绾﹀畾锛汚piDomain 宸叉敼鐜板満鍦板潃鐨勭函瀹瑰櫒閮ㄧ讲鍙拷鐣ユ湰椤癸級
+# Git Bash 涓� /etc/hosts 鏄� Git 鑷甫鏂囦欢锛學indows 鐪� hosts 鍦� System32/drivers/etc/hosts
+HOSTS_FILE=/etc/hosts
+case "$(uname -s)" in MINGW*|MSYS*) HOSTS_FILE="$(cygpath -u "$SYSTEMROOT")/System32/drivers/etc/hosts" ;; esac
+if grep -q 'cx-infra' "$HOSTS_FILE" 2>/dev/null; then
+  ok "瀹夸富 hosts 宸插惈 cx-infra"
+else
+  warn2 "瀹夸富 hosts 缂� cx-infra" "琛ヤ竴琛屻��127.0.0.1 cx-infra銆嶏紙鏈満娴忚鍣ㄨ皟璇曠敤锛汚piDomain 宸叉敼涓虹幇鍦哄湴鍧�鐨勭函瀹瑰櫒閮ㄧ讲鍙拷鐣ワ級"
+fi
+
+# 锛圧ocketMQ 宸蹭簬 2026-08-10 绉婚櫎锛屽師 mq-store 鍗峰睘涓绘鏌ヤ竴骞跺垹闄わ紝瑙佽ˉ涓� #10锛�
+
+# 鈹�鈹� 1. .env 蹇呭~閿紙浠� compose 鏂囦欢鐨� \${VAR:?} 鍔ㄦ�佹彁鍙栵紝鏀� compose 鑷姩璺熻繘锛� 鈹�鈹�
+log "===== 1/6 .env 蹇呭~閿� ====="
+ENV_OK=1
+# 缂栨帓鏂囦欢瀛樺湪鎬у凡鍦� 0/6 纭墠缃鏌ワ紙鏃╀簬鏈妭瀵硅鏂囦欢鐨� awk/grep 璇诲彇锛�
+if [ ! -f "$ENV_FILE" ]; then
+  ENV_OK=0
+  bad "缂� ${ENV_FILE}锛坈ompose 鍑嵁鏉ユ簮锛�" "cp .env.example .env 骞舵寜娉ㄩ噴鏀瑰�硷紙浜や粯鐜蹇呴』鍏ㄩ儴鏀瑰瘑锛岃 docs/release-checklist.md 搂0.7锛�"
+else
+  # 鍏堟护娉ㄩ噴琛屽啀鎻愬彇锛坈ompose 澶存敞閲婇噷鏈� ${VAR:?} 瀛楁牱鐨勮鏄庢枃瀛楋紝涓嶆护浼氳鎻愬彇锛�
+  REQUIRED_KEYS="$(grep -vE '^[[:space:]]*#' "$COMPOSE_FILE_PATH" | grep -oE '\$\{[A-Za-z_][A-Za-z0-9_]*:\?' | sed -E 's/^\$\{//; s/:\?$//' | sort -u)"
+  MISSING=()
+  for key in $REQUIRED_KEYS; do
+    # .env 閲屾湁闈炵┖鍊硷紝鎴栧凡鍦ㄧ幆澧冨彉閲忛噷锛坈ompose 涓ょ鏉ユ簮閮借锛�
+    if grep -qE "^${key}=..*" "$ENV_FILE" || [ -n "${!key:-}" ]; then :; else MISSING+=("$key"); fi
+  done
+  if [ "${#MISSING[@]}" -eq 0 ]; then
+    ok "蹇呭~閿綈锛�$(echo "$REQUIRED_KEYS" | tr '\n' ' ')"
+  else
+    ENV_OK=0
+    bad "${ENV_FILE} 缂哄繀濉敭锛�${MISSING[*]}" "瀵圭収 .env.example 琛ラ綈锛堟瘡涓敭鐨勭敓鎴愭柟娉曡鏂囦欢鍐呮敞閲婏級"
+  fi
+fi
+
+# 鈹�鈹� 2. 瀹㈡埛鍧愭爣锛坉ocs/adr-multi-customer-db-isolation.md 搂3锛夆攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+# 缂洪厤浼氶潤榛樺洖閫�鍒伴粯璁� cx-postgres:5432鈥斺�斿湪寮�鍙戞満涓婂氨鏄繛鍒版湰鏈烘矙鐩掕�屼笉鏄鎴峰簱锛�
+# 鏁版嵁鍐欓敊鍦版柟涓旀棤浠讳綍鎻愮ず銆傝繖涓や釜鍙橀噺鍚屾椂鍐冲畾涓氬姟搴� jnpf_init 涓庡紩鎿庡簱 jnpf_flow
+# 锛坈ompose 鐨� FLOW_DB_URL 鐢卞畠浠帹瀵硷級锛屾晠涓ゅ簱涓嶅彲鑳藉崐鍒囥��
+log "===== 2/6 瀹㈡埛鍧愭爣 ====="
+if [ "$ENV_OK" = 0 ]; then
+  skip "瀹㈡埛鍧愭爣"
+else
+  # `|| true` 涓嶅彲鐪侊細set -euo pipefail 涓� grep 鏃犲尮閰嶈繑鍥� 1锛屼細缁� pipefail 浼犵粰鍛戒护鏇挎崲锛�
+  # 浠よ祴鍊煎け璐ュ苟琚� set -e 鐩存帴缁堟鑴氭湰鈥斺�旇〃鐜颁负鎵撳畬鑺傛爣棰樺氨闈欓粯閫�鍑猴紙缂洪厤鎭版伆鏄鎶ョ殑鎯呭喌锛�
+  CDH="$(grep -E '^CUSTOMER_DB_HOST=' "$ENV_FILE" | tail -1 | cut -d= -f2- || true)"
+  CDP="$(grep -E '^CUSTOMER_DB_PORT=' "$ENV_FILE" | tail -1 | cut -d= -f2- || true)"
+  if [ -z "$CDH" ] || [ -z "$CDP" ]; then
+    bad "${ENV_FILE} 缂� CUSTOMER_DB_HOST / CUSTOMER_DB_PORT" \
+        "浠� .env.example 鎶勮繖涓よ锛氬悓鏍� PG 濉� cx-postgres / 5432锛涘鎴峰簱濉� YOUR_DB_HOST / 154xx锛堝鎴�1=15432銆佸鎴�2=15433锛�"
+  elif [ -n "$(printf '%s' "$CDP" | tr -d '0-9')" ]; then
+    bad "CUSTOMER_DB_PORT 蹇呴』鏄暣鏁帮紝褰撳墠='${CDP}'" "鏀� ${ENV_FILE}"
+  else
+    echo "    鐢熸晥鍧愭爣锛�${CDH}:${CDP}"
+    # 鏍堝唴 PG 鏀跺湪 localdb profile 鍐咃紙榛樿涓嶅惎鍔級锛屽紑鍏冲繀椤讳笌鍧愭爣閰嶅锛屽惁鍒欎袱绉嶉敊閰嶏細
+    #   鍧愭爣鎸囨爤鍐呭嵈娌″紑 profile 鈫� PG 鏍规湰涓嶈捣锛屽叏鏍堟棤搴撳彲杩烇紙纭け璐ワ級
+    #   鍧愭爣鎸囪繙绔嵈寮�浜� profile 鈫� 璧蜂竴涓病浜鸿繛鐨� PG锛岃繕瑕佹眰 external 鍗峰瓨鍦紙璀﹀憡锛�
+    PROFILES="$(printf '%s' "${COMPOSE_PROFILES:-$(get_env COMPOSE_PROFILES)}" | tr ' ' ',')"
+    case ",${PROFILES}," in *,localdb,*) HAS_LOCALDB=1 ;; *) HAS_LOCALDB=0 ;; esac
+    if [ "$CDH" = "cx-postgres" ] && [ "$HAS_LOCALDB" = 0 ]; then
+      bad "CUSTOMER_DB_HOST=cx-postgres 浣嗘湭鍚敤 localdb profile锛堟爤鍐� PG 涓嶄細鍚姩锛屼笟鍔″叏绾挎棤搴撳彲杩烇級" \
+          "鍦� ${ENV_FILE} 鍔犱竴琛� COMPOSE_PROFILES=localdb锛堣 .env.example 搂1锛�"
+    elif [ "$CDH" != "cx-postgres" ] && [ "$HAS_LOCALDB" = 1 ]; then
+      warn2 "搴撳湪杩滅 ${CDH}:${CDP}锛屽嵈鍚敤浜� localdb profile" \
+          "娉ㄩ噴鎺� ${ENV_FILE} 鐨� COMPOSE_PROFILES=localdb鈥斺�斿惁鍒欎細璧蜂竴涓病浜鸿繛鐨� PG锛屼笖缂� external 鍗� ${VOLUME_NAME} 鏃舵暣鏍堣捣涓嶆潵"
+    fi
+    if [ "$CDH" = "cx-postgres" ]; then
+      # compose 鍐呴儴鏈嶅姟鍚嶏紝瀹夸富瑙f瀽涓嶄簡锛涜捣鏍堝悗鐢� PG healthcheck 涓庝笅闈� 3/6 淇濊瘉
+      ok "瀹㈡埛鍧愭爣 = 鍚屾爤 PG锛�${CDH}:${CDP}锛夛紝璺宠繃瀹夸富瀹炶繛鎺㈡祴"
+    elif command -v nc >/dev/null 2>&1 && nc -z -w 5 "$CDH" "$CDP" >/dev/null 2>&1; then
+      ok "瀹㈡埛鍧愭爣 ${CDH}:${CDP} 鍙揪"
+    elif ! command -v nc >/dev/null 2>&1 && \
+         (exec 3<>"/dev/tcp/${CDH}/${CDP}") >/dev/null 2>&1; then
+      # Git Bash 鏃� nc锛岄��鍥� bash /dev/tcp锛堟棤瓒呮椂鎺у埗锛屼笉鍙揪鏃跺彲鑳界瓑鍒� TCP SYN 瓒呮椂锛�
+      ok "瀹㈡埛鍧愭爣 ${CDH}:${CDP} 鍙揪锛�/dev/tcp 鎺㈡祴锛�"
+    else
+      bad "瀹㈡埛鍧愭爣 ${CDH}:${CDP} 杩炰笉涓�" \
+          "纭绔彛濉锛堝鎴�1=15432 / 瀹㈡埛2=15433锛夈�佷簯瀹夊叏缁勫凡鏀捐鏉ユ簮 IP銆佹湰鏈虹綉缁滃嚭绔欎笉鍙楅檺"
+    fi
+  fi
+
+  # Compose 鐢熸晥鐨� Redis 鍧愭爣銆侸NPF_DOCKER_REDIS_* 鍏佽瀹夸富 JVM 鐢� 127.0.0.1銆�
+  # 瀹瑰櫒鏀硅蛋 cx-infra(host-gateway)锛岄伩鍏嶅鍣ㄦ妸 127.0.0.1 璇綋鎴愬涓绘満銆�
+  RDH_HOST="$(grep -E '^JNPF_REDIS_HOST=' "$ENV_FILE" | tail -1 | cut -d= -f2- || true)"
+  RDP_HOST="$(grep -E '^JNPF_REDIS_PORT=' "$ENV_FILE" | tail -1 | cut -d= -f2- || true)"
+  RDH="$(grep -E '^JNPF_DOCKER_REDIS_HOST=' "$ENV_FILE" | tail -1 | cut -d= -f2- || true)"
+  RDP="$(grep -E '^JNPF_DOCKER_REDIS_PORT=' "$ENV_FILE" | tail -1 | cut -d= -f2- || true)"
+  RDH="${RDH:-${RDH_HOST:-cx-redis}}"; RDP="${RDP:-${RDP_HOST:-6379}}"
+  echo "    Compose Redis 鍧愭爣锛�${RDH}:${RDP}"
+  if [ "$RDH" = "cx-redis" ]; then
+    ok "Redis 鍧愭爣 = 鍚屾爤 redis锛�${RDH}:${RDP}锛夛紝璺宠繃瀹夸富瀹炶繛鎺㈡祴"
+  else
+    # 杩欎袱涓悕绉板彧鍦ㄥ鍣ㄥ唴鎸囧悜 host-gateway锛涢妫�杩愯鍦ㄥ涓伙紝绛変环鎺㈡祴鍥炵幆鍦板潃銆�
+    REDIS_PROBE_HOST="$RDH"
+    case "$RDH" in
+      cx-infra|host.docker.internal) REDIS_PROBE_HOST=127.0.0.1 ;;
+    esac
+    if command -v nc >/dev/null 2>&1 && nc -z -w 5 "$REDIS_PROBE_HOST" "$RDP" >/dev/null 2>&1; then
+      ok "Redis 鍧愭爣 ${RDH}:${RDP} 鍙揪"
+    elif ! command -v nc >/dev/null 2>&1 && \
+         (exec 3<>"/dev/tcp/${REDIS_PROBE_HOST}/${RDP}") >/dev/null 2>&1; then
+      ok "Redis 鍧愭爣 ${RDH}:${RDP} 鍙揪锛�/dev/tcp 鎺㈡祴锛�"
+    else
+      bad "Redis 鍧愭爣 ${RDH}:${RDP} 杩炰笉涓�" \
+          "瀹夸富鑷 Redis 璇疯 JNPF_DOCKER_REDIS_HOST=cx-infra锛屽苟纭 Redis 姝g洃鍚搴旂鍙o紱杩滅瀹炰緥纭浜戝畨鍏ㄧ粍宸叉斁琛屾潵婧� IP"
+    fi
+  fi
+fi
+
+# 鈹�鈹� 澶栭儴搴撴ā寮忓垽瀹� 鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+# 鍧愭爣鎸� CVM/杩滅▼瀹炰緥鏃讹紝鏈満 cx-postgres 涓嶆槸鏁版嵁闈紙娴嬭瘯鐜宸叉寜 testenv.yml 鐢�
+# profiles 鎶婂畠璇峰嚭鏍堬級锛�3/6-6/6 鐨勬湰鍦板嵎/瀹瑰櫒/schema/绉嶅瓙妫�鏌ュ叏閮ㄨ烦杩団�斺�斿惁鍒� 4/6
+# 浼氭妸宸插仠鐢ㄧ殑鏈湴 PG 鍙堟媺璧锋潵銆傝繙绋嬪簱杩為�氭�� 2/6 宸插疄杩炴帰娴嬶紱schema/瀵嗙爜鐢� CVM 渚�
+# 绠$悊 + 鏈嶅姟鍚姩杩炲簱琛屼负鍏滃簳銆�
+EXTERNAL_DB=0
+if [ "$ENV_OK" = 1 ] && [ -n "${CDH:-}" ] && [ "$CDH" != "cx-postgres" ]; then
+  EXTERNAL_DB=1
+fi
+
+# 鈹�鈹� 3. PG 鏁版嵁鍗� 鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+log "===== 3/6 鏁版嵁鍗� ${VOLUME_NAME} ====="
+VOL_OK=1
+if [ "$EXTERNAL_DB" = 1 ]; then
+  VOL_OK=0
+  skip "鏁版嵁鍗锋鏌ワ紙鏈湴 PG 闈炴暟鎹潰锛�" "澶栭儴搴撴ā寮� ${CDH}:${CDP}"
+elif docker volume inspect "$VOLUME_NAME" >/dev/null 2>&1; then
+  ok "external 鍗� ${VOLUME_NAME} 瀛樺湪"
+else
+  VOL_OK=0
+  bad "external 鍗� ${VOLUME_NAME} 涓嶅瓨鍦紙compose 澹版槑 external锛岀己鍗风洿鎺ヨ捣涓嶆潵锛�" \
+      "docker volume create ${VOLUME_NAME}锛屽啀鐏屾暟鎹細( cd ${MIG_DIR} && ./install.sh && ./create-app-roles.sh )锛圛NSTALL.md 搂2 鈶わ級"
+fi
+
+# 鈹�鈹� 4. PG 瀹瑰櫒鍙捣涓旀帴鍙楄繛鎺� 鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+log "===== 4/6 PG 瀹瑰櫒 ${CONTAINER} ====="
+PG_OK=0
+if [ "$EXTERNAL_DB" = 1 ]; then
+  skip "PG 瀹瑰櫒妫�鏌ワ紙涓嶆媺璧锋湰鍦� ${CONTAINER}锛�" "澶栭儴搴撴ā寮�"
+elif docker exec "$CONTAINER" true >/dev/null 2>&1; then
+  PG_OK=1
+elif [ "$VOL_OK" = 1 ] && [ "$ENV_OK" = 1 ]; then
+  log "瀹瑰櫒鏈湪杩愯锛屽皾璇曟媺璧凤細docker compose up -d cx-postgres"
+  if docker compose up -d cx-postgres >/dev/null 2>&1; then
+    PG_OK=1
+  else
+    bad "PG 瀹瑰櫒鎷夎捣澶辫触锛坉ocker compose up -d cx-postgres锛�" \
+        "闀滃儚缂哄け鍒欏厛 make images锛堜粨搴擄級/ make load锛堢绾垮寘锛夛紱鍐嶇湅 docker compose logs cx-postgres"
+  fi
+else
+  skip "PG 瀹瑰櫒鎷夎捣锛�.env 鎴栨暟鎹嵎鏈氨缁級"
+fi
+
+if [ "$PG_OK" = 1 ]; then
+  waited=0
+  until docker exec "$CONTAINER" pg_isready -q -U postgres >/dev/null 2>&1; do
+    if [ "$waited" -ge "$PG_WAIT_SECS" ]; then
+      PG_OK=0
+      bad "PG ${PG_WAIT_SECS}s 鍐呮湭鎺ュ彈杩炴帴锛堝ぇ鍗� crash recovery 鍙兘鏇翠箙锛�" \
+          "docker compose logs cx-postgres 鐪嬫仮澶嶈繘搴�/鎶ラ敊锛涚‘璁ゅ唴瀛樺厖瓒筹紙L024锛夊悗鍙皟澶� PG_WAIT_SECS 閲嶈窇"
+      break
+    fi
+    [ "$waited" = 0 ] && log "绛夊緟 PG 鎺ュ彈杩炴帴锛堜笂闄� ${PG_WAIT_SECS}s锛屽喎鍚姩/crash recovery 灞炴甯革級鈥�"
+    sleep 3; waited=$((waited+3))
+  done
+  [ "$PG_OK" = 1 ] && ok "PG 鎺ュ彈杩炴帴锛坧g_isready锛�"
+fi
+
+# 鈹�鈹� 5. 涓夊簱 schema锛氬簱瀛樺湪 + 琛ㄦ暟 鈮� 鍩哄噯 鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+log "===== 5/6 涓夊簱 schema ====="
+DBS_OK=1
+if [ "$EXTERNAL_DB" = 1 ]; then
+  DBS_OK=0
+  skip "涓夊簱 schema 鏍¢獙" "澶栭儴搴撴ā寮�"
+elif [ "$PG_OK" = 1 ]; then
+  check_db() {
+    local db="$1" base="$2" n
+    if ! psql_exec -tAc "SELECT 1 FROM pg_database WHERE datname='$db'" 2>/dev/null | grep -q 1; then
+      DBS_OK=0
+      bad "搴� ${db} 涓嶅瓨鍦�" "鍏ㄦ柊鐜璺� ( cd ${MIG_DIR} && ./install.sh )锛堝缓鍥涘簱+schema锛汭NSTALL.md 搂2 鈶わ級"
+      return
+    fi
+    n="$(psql_exec -d "$db" -tAc "SELECT count(*) FROM information_schema.tables WHERE table_schema='public' AND table_type='BASE TABLE'" 2>/dev/null || echo 0)"
+    if [ "${n:-0}" -ge "$base" ]; then
+      ok "搴� ${db}锛�${n} 寮犺〃锛堝熀鍑� 鈮�${base}锛�"
+    else
+      DBS_OK=0
+      bad "搴� ${db} 琛ㄦ暟 ${n} < 鍩哄噯 ${base}锛坰chema 涓嶅畬鏁�/鐏屽簱涓柇锛�" \
+          "閲嶈窇 ( cd ${MIG_DIR} && ./install.sh )鈥斺�旂洰鏍囧簱闈炵┖浼氳瀹堝崼鎷︿笅锛屾寜鎻愮ず FORCE_RESEED=1锛堜細娓呰搴撴暟鎹紝鍏堢‘璁ゅ綊灞烇級"
+    fi
+  }
+  check_db "$DB_INIT"   "$BASE_INIT"
+  check_db "$DB_FLOW"   "$BASE_FLOW"
+  check_db "$DB_XXLJOB" "$BASE_XXLJOB"
+else
+  DBS_OK=0
+  skip "涓夊簱 schema 鏍¢獙"
+fi
+
+# 鈹�鈹� 6. 绉嶅瓙鏁版嵁 + 鍏变韩閰嶇疆鏂囦欢 + 涓氬姟瑙掕壊 鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+log "===== 6/6 绉嶅瓙鏁版嵁銆佸叡浜厤缃枃浠朵笌涓氬姟瑙掕壊 ====="
+# 鍏变韩閰嶇疆鏂囦欢灏变綅锛堣ˉ涓� #8锛氭浛浠e師閰嶇疆涓績绉嶅瓙妫�鏌ワ級鈥斺�斾笌搴撴棤鍏筹紝澶栭儴搴撴ā寮忎篃瑕佹煡
+for f in datasource resources system-config frame-config logger mq discovery; do
+  if [ -f "config/shared/$f.yaml" ]; then
+    ok "config/shared/${f}.yaml 灏变綅"
+  else
+    bad "缂� config/shared/${f}.yaml锛堜笟鍔℃湇鍔″彇涓嶅埌璇ラ」閰嶇疆锛岃捣鏉ヤ篃鍏ㄦ寕锛�" \
+        "纭浠撳簱鍐� config/shared/ 鐩綍瀹屾暣锛堣ˉ涓� #8 鍚庨厤缃殢浠撳簱鍒嗗彂锛屼笉鍐嶇粡閰嶇疆涓績涓嬪彂锛�"
+  fi
+done
+
+if [ "$PG_OK" = 1 ] && [ "$DBS_OK" = 1 ]; then
+  n_admin="$(psql_exec -d "$DB_INIT" -tAc "SELECT count(*) FROM base_user WHERE f_account='admin'" 2>/dev/null || echo 0)"
+  if [ "${n_admin:-0}" -ge 1 ]; then
+    ok "绉嶅瓙鐢ㄦ埛 admin 鍦紙${DB_INIT}.base_user锛�"
+  else
+    bad "${DB_INIT}.base_user 鏃� admin锛堢瀛愭暟鎹己澶憋紝鐧诲綍/鍐掔儫蹇呭け璐ワ級" \
+        "鍏ㄦ柊鐜锛歩nstall.sh 鐨勫畼鏂� init 鍚瀛愶紱鍓叉帴鐜锛氭寜 docs/archive/server-pg-cutover-checklist.md 璺� migrate-data.sh"
+  fi
+
+  # 涓氬姟瑙掕壊锛堣鑹插悕浠� .env 涓哄噯锛岀己鐪�=浜や粯鏍囧噯鍊硷紱瀵嗙爜鏄惁鍖归厤 preflight 涓嶉獙鈥斺�旇 L030锛�
+  # socket/127.0.0.1 鏄� trust 鍋囬獙璇侊紝鐪熷疄楠岃瘉鐢辨湇鍔″惎鍔ㄥ悗鐨勮繛搴撹涓哄厹搴曪級
+  APP_ROLE="$(get_env JNPF_APP_DB_USER)";   APP_ROLE="${APP_ROLE:-jnpf_app}"
+  FLOW_ROLE="$(get_env JNPF_FLOW_DB_USER)"; FLOW_ROLE="${FLOW_ROLE:-jnpf_flow_app}"
+  for role in "$APP_ROLE" "$FLOW_ROLE"; do
+    if psql_exec -tAc "SELECT 1 FROM pg_roles WHERE rolname='$role'" 2>/dev/null | grep -q 1; then
+      ok "涓氬姟瑙掕壊 ${role} 鍦�"
+    else
+      bad "涓氬姟瑙掕壊 ${role} 涓嶅瓨鍦紙瀵瑰簲鏈嶅姟杩炲簱蹇呮姤 password authentication failed锛�" \
+          "鍏堝畾绋� ${ENV_FILE} 鍐嶈窇 ( cd ${MIG_DIR} && ./create-app-roles.sh )锛堝箓绛夛紝鎸� .env 閲嶇疆瀵嗙爜锛浡�0.11 閾佸緥椤哄簭锛�"
+    fi
+  done
+else
+  [ "$EXTERNAL_DB" = 1 ] && skip "绉嶅瓙鏁版嵁涓庝笟鍔¤鑹叉牎楠岋紙鐢� CVM 渚х鐞嗭級" "澶栭儴搴撴ā寮�" \
+                         || skip "绉嶅瓙鏁版嵁涓庝笟鍔¤鑹叉牎楠�"
+fi
+
+# 鈹�鈹� 姹囨�� 鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+echo
+if [ "${#WARNS[@]}" -gt 0 ]; then
+  warnp "璀﹀憡 ${#WARNS[@]} 椤癸紙涓嶆嫤鎴級锛�"
+  for w in "${WARNS[@]}"; do printf '  路 %s\n' "$w"; done
+fi
+if [ "${#FAILS[@]}" -eq 0 ]; then
+  log "preflight 閫氳繃 鉁擄紙鍙户缁� docker compose up锛�"
+else
+  printf '\033[1;31m[preflight][err]\033[0m 鏈�氳繃 %s 椤癸細\n' "${#FAILS[@]}" >&2
+  i=1
+  for f in "${FAILS[@]}"; do printf '  %2d. %s\n' "$i" "$f" >&2; i=$((i+1)); done
+  printf '\033[1;31m[preflight][err]\033[0m 淇鍚庨噸璺� make preflight锛涚‘瑕佽烦杩囷細SKIP_PREFLIGHT=1 make up\n' >&2
+  exit 1
+fi

--
Gitblit v1.8.0