#!/usr/bin/env bash
|
# ─────────────────────────────────────────────────────────────────────────────
|
# JNPF 全栈部署自举校验(Backlog C1)
|
# make up / make up-no-pull 的强制前置(SKIP_PREFLIGHT=1 逃生),也可独立运行。
|
# 目标:把"空库 PG 却 healthy、引擎/业务全线连不上"的静默失败拦在
|
# compose up 之前,缺项直接给出修复指引(migration/install.sh、INSTALL.md)。
|
#
|
# 校验分三层:
|
# 硬前置(失败立即退出) :docker CLI / daemon 可达 / compose v2 插件 / 编排文件存在
|
# 聚合检查(跑完统一汇总):.env 必填键(从 compose 的 ${VAR:?} 动态提取)、
|
# 客户坐标(CUSTOMER_DB_HOST/PORT 必填 + 实连探测 + 与 localdb profile 配套)、
|
# postgres_pgdata 卷、PG 容器可起且接受连接、三库表数 ≥ 基准、
|
# 种子数据(jnpf_init 有 admin)、共享配置文件就位(补丁 #8)、业务角色齐
|
# 警告(不拦截):Docker 可用内存 <12GB(L024)、宿主 hosts 缺 cx-infra、
|
#
|
# 两种库形态,检查项按 CUSTOMER_DB_HOST 自动分流(2026-08-07):
|
# 栈内 PG(=cx-postgres,交付/正式):全套跑,含卷/容器/三库/种子/角色
|
# 远端客户库(=CVM 上的客户实例,开发机/测试环境):3-5 与角色检查跳过——那边的
|
# 卷、schema、种子、角色由该 PG 实例自身的运维负责(docs/db-connection-pool-guide.md),
|
# 本机既起不了那个容器也 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 已启动,且当前用户有权限(docker info 报错内容为准)"
|
docker compose version >/dev/null 2>&1 \
|
|| die "docker compose v2 插件不可用——需要 Docker Compose V2(docker compose version 验证)"
|
ok "docker CLI / daemon / compose v2 就绪"
|
|
# 编排文件存在性提前到此处硬前置检查(原放在下方"1. .env 必填键",但下面若干
|
# 检查更早就用 awk 读取该文件——set -e 下 awk 遇文件不存在会以 awk 自身退出码裸退,绕过所有
|
# die() 提示;提到最前面确保任何读取 COMPOSE_FILE_PATH 的分支之前已保证文件存在)
|
[ -f "$COMPOSE_FILE_PATH" ] \
|
|| die "缺编排文件 ${COMPOSE_FILE_PATH}——请在仓库根/离线交付包根目录执行"
|
|
# 内存红线(L024:全栈 16 容器 <12GB 会 OOM 连锁:PG crash 循环 + gRPC 超时风暴)
|
# 四舍五入到 GiB:VM 名义 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:Linux 加物理内存/减容器;OrbStack 用 orb config set memory_mib 12288(L024)"
|
else
|
ok "Docker 可用内存 ${MEM_GIB}GB(≥12GB)"
|
fi
|
|
# cx-infra 宿主解析(ApiDomain 双消费方约定;ApiDomain 已改现场地址的纯容器部署可忽略本项)
|
# Git Bash 下 /etc/hosts 是 Git 自带文件,Windows 真 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」(本机浏览器调试用;ApiDomain 已改为现场地址的纯容器部署可忽略)"
|
fi
|
|
# (RocketMQ 已于 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}(compose 凭据来源)" "cp .env.example .env 并按注释改值(交付环境必须全部改密,见 docs/release-checklist.md §0.7)"
|
else
|
# 先滤注释行再提取(compose 头注释里有 ${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 里有非空值,或已在环境变量里(compose 两种来源都认)
|
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. 客户坐标(docs/adr-multi-customer-db-isolation.md §3)────────────────
|
# 缺配会静默回退到默认 cx-postgres:5432——在开发机上就是连到本机沙盒而不是客户库,
|
# 数据写错地方且无任何提示。这两个变量同时决定业务库 jnpf_init 与引擎库 jnpf_flow
|
# (compose 的 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 内部服务名,宿主解析不了;起栈后由 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 坐标。JNPF_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 正监听对应端口;远端实例确认云安全组已放行来源 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 )(INSTALL.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 容器拉起失败(docker 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 接受连接(pg_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;INSTALL.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}(schema 不完整/灌库中断)" \
|
"重跑 ( 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:替代原配置中心种子检查)——与库无关,外部库模式也要查
|
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(种子数据缺失,登录/冒烟必失败)" \
|
"全新环境:install.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
|