#!/usr/bin/env bash # Backlog A4:创建业务专用连库角色(jnpf_app / jnpf_flow_app / nacos_app)并移交存量对象所有权。 # 幂等,可重复执行;密码取自仓库根 .env(键见下),执行后按 .env 重置角色密码。 # # 前置:目标 PG 容器在跑 # # 参数(环境变量): # CONTAINER 目标 PG 容器名,默认 cx-postgres # ENV_FILE 凭据文件,默认从脚本目录向上找第一个 .env # # 🔴 本脚本会 ALTER ROLE ... PASSWORD 【重置】角色密码。多实例机器上跑错 CONTAINER # =把另一个客户的密码改掉、配额改掉,业务当场连不上。开跑前必看它打印的目标容器。 # 用法:./docker/postgres/migration/create-app-roles.sh(仓库根) # 或 migration/create-app-roles.sh(离线交付包内,.env 在包根) # # 执行后的联动(手工,见 docs/release-checklist.md §0.3): # 1. config/shared/datasource.yaml:spring.datasource.username/password → jnpf_app # 2. .env:JNPF_FLOW_DB_USER/PASSWORD → jnpf_flow_app;JNPF_NACOS_DB_USER/PASSWORD → nacos_app # 3. docker compose up -d(重建 cx-flow-engine)+ 重启业务服务 set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # 目标容器:多实例机器上必须显式传,默认值只为兼容单实例的历史调用 CONTAINER="${CONTAINER:-cx-postgres}" docker inspect "$CONTAINER" >/dev/null 2>&1 || { echo "错误:容器 $CONTAINER 不在"; exit 1; } printf '\033[1;33m目标 PG 容器:%s\033[0m(本脚本会重置该实例的角色密码与连接配额)\n' "$CONTAINER" # 凭据文件:优先 ENV_FILE 显式指定,否则从脚本目录向上找第一个含 .env 的目录(仓库根/交付包根)。 # # ENV_FILE 是为多客户拓扑加的(docs/adr-multi-customer-db-isolation.md §8):一台机器上跑着多个 # 客户的 PG 实例,而密码与连接配额是【按实例】不同的。没有这个开关就只能"临时改共享 .env → 跑脚本 # → 记得改回来",漏改一次就会在下次对别的实例跑本脚本时把那个客户的密码改成这个客户的。 # 用法:ENV_FILE=/home/ubuntu/jnpf-pg/.env.c2 CONTAINER=cx-pg-c2 ./create-app-roles.sh if [ -n "${ENV_FILE:-}" ]; then [ -f "$ENV_FILE" ] || { echo "错误:ENV_FILE 指定的 $ENV_FILE 不存在"; exit 1; } ENV_PATH="$ENV_FILE" else ROOT=""; d="$SCRIPT_DIR" while [ "$d" != "/" ]; do if [ -f "$d/.env" ]; then ROOT="$d"; break; fi d="$(dirname "$d")" done [ -n "$ROOT" ] || { echo "错误:向上未找到 .env(在仓库根/交付包根 cp .env.example .env 并定稿后重试,L030)"; exit 1; } ENV_PATH="$ROOT/.env" fi echo "凭据文件:$ENV_PATH" # 只按键取值,不 source(.env 可能含任意字符)。 # `|| true` 不可省:set -euo pipefail 下 grep 无匹配返回 1,经 pipefail 传给命令替换会令赋值失败 # 并被 set -e 直接终止脚本——下面那些"缺 XXX"的友好报错反而永远走不到。 envval() { grep -E "^$1=" "$ENV_PATH" | tail -1 | cut -d= -f2- || true; } APP_PW="$(envval JNPF_APP_DB_PASSWORD)" FLOW_PW="$(envval JNPF_FLOW_DB_PASSWORD)" NACOS_PW="$(envval JNPF_NACOS_DB_PASSWORD)" for pair in "JNPF_APP_DB_PASSWORD:${APP_PW}" "JNPF_FLOW_DB_PASSWORD:${FLOW_PW}"; do [ -n "${pair#*:}" ] || { echo "错误:.env 缺 ${pair%%:*}(模板见 .env.example)"; exit 1; } done # nacos_app:补丁 #8 后新栈不再使用,.env.example 已不含 JNPF_NACOS_DB_PASSWORD。 # 只有运维显式在 ${ENV_FILE:-.env} 里设置该键(兼容仍跑旧 Nacos 架构的实例)时才处理这个角色; # 缺键时不当错误处理,直接跳过,不许因此报错退出。 NACOS_ENABLED=false if [ -n "$NACOS_PW" ]; then NACOS_ENABLED=true else echo "跳过 nacos_app(补丁 #8 后栈不再使用;旧实例兼容需自行设 JNPF_NACOS_DB_PASSWORD)" fi # 连接数配额(L029 纵深防御:防单角色占满连接槽饿死流程引擎等)。缺省 = 正式/单套环境推荐值; # CVM 共享库在 .env 覆盖为 350/35/10。铁律:三者之和须 < (max_connections − superuser_reserved)。 APP_CONN_LIMIT="$(envval JNPF_APP_CONN_LIMIT)"; APP_CONN_LIMIT="${APP_CONN_LIMIT:-150}" FLOW_CONN_LIMIT="$(envval JNPF_FLOW_CONN_LIMIT)"; FLOW_CONN_LIMIT="${FLOW_CONN_LIMIT:-30}" NACOS_CONN_LIMIT="$(envval JNPF_NACOS_CONN_LIMIT)"; NACOS_CONN_LIMIT="${NACOS_CONN_LIMIT:-10}" # 校验为非负整数(psql -v 直接内插进 CONNECTION LIMIT,非整数会致语法错/注入) # nacos_app 未启用时不校验其配额——不参与求和也不会被用到,避免遗留脏值挡住 app/flow 两个角色 CONN_CHECK_PAIRS=("JNPF_APP_CONN_LIMIT:${APP_CONN_LIMIT}" "JNPF_FLOW_CONN_LIMIT:${FLOW_CONN_LIMIT}") [ "$NACOS_ENABLED" = true ] && CONN_CHECK_PAIRS+=("JNPF_NACOS_CONN_LIMIT:${NACOS_CONN_LIMIT}") for pair in "${CONN_CHECK_PAIRS[@]}"; do case "${pair#*:}" in ''|*[!0-9]*) echo "错误:${pair%%:*} 必须是非负整数,当前='${pair#*:}'(.env 或改默认)"; exit 1;; esac done # 提示文案按实际处理的角色数计算(nacos_app 跳过时不计入求和) if [ "$NACOS_ENABLED" = true ]; then echo "连接配额:jnpf_app=${APP_CONN_LIMIT} / jnpf_flow_app=${FLOW_CONN_LIMIT} / nacos_app=${NACOS_CONN_LIMIT}(和=$((APP_CONN_LIMIT+FLOW_CONN_LIMIT+NACOS_CONN_LIMIT)),须 < max_connections−3)" else echo "连接配额:jnpf_app=${APP_CONN_LIMIT} / jnpf_flow_app=${FLOW_CONN_LIMIT}(和=$((APP_CONN_LIMIT+FLOW_CONN_LIMIT)),须 < max_connections−3;nacos_app 已跳过,不计入)" fi docker exec -i "$CONTAINER" psql -U postgres -d postgres \ -v ON_ERROR_STOP=1 \ -v nacos_enabled="${NACOS_ENABLED}" \ -v app_pw="${APP_PW}" \ -v flow_pw="${FLOW_PW}" \ -v nacos_pw="${NACOS_PW}" \ -v app_conn_limit="${APP_CONN_LIMIT}" \ -v flow_conn_limit="${FLOW_CONN_LIMIT}" \ -v nacos_conn_limit="${NACOS_CONN_LIMIT}" \ -f - < "$SCRIPT_DIR/create_app_roles.sql" echo echo "== 验证:各角色 TCP 连各自库(容器 eth0 地址,走 scram 真验密码;socket/127.0.0.1 是 trust 验不出错密码) ==" PGIP=$(docker exec "$CONTAINER" hostname -i | awk '{print $1}') docker exec "$CONTAINER" psql "postgresql://jnpf_app:${APP_PW}@${PGIP}:5432/jnpf_init" -Atc "SELECT 'jnpf_app→jnpf_init OK'" docker exec "$CONTAINER" psql "postgresql://jnpf_flow_app:${FLOW_PW}@${PGIP}:5432/jnpf_flow" -Atc "SELECT 'jnpf_flow_app→jnpf_flow OK'" if [ "$NACOS_ENABLED" = true ]; then docker exec "$CONTAINER" psql "postgresql://nacos_app:${NACOS_PW}@${PGIP}:5432/nacos_config" -Atc "SELECT 'nacos_app→nacos_config OK'" fi echo "== 验证:越库应被拒(期望报 permission denied) ==" docker exec "$CONTAINER" psql "postgresql://jnpf_app:${APP_PW}@${PGIP}:5432/nacos_config" -Atc "SELECT 1" 2>&1 | head -1 || true echo "完成 ✓"