#!/usr/bin/env bash # ───────────────────────────────────────────────────────────────────────────── # 开一个客户的 PG 实例(docs/adr-multi-customer-db-isolation.md §8)。 # # 只是把四个【已有且都支持 CONTAINER 参数】的脚本串起来,本身不含任何建库逻辑: # compose up → install.sh(建四库+schema)→ create-app-roles.sh(角色+配额) # → harden-pg-hba.sh(postgres 超户禁公网) # 幂等:容器已在跑会跳过重建;install.sh 对非空库会自己拒绝(除非 FORCE_RESEED=1)。 # # 用法(在 /home/ubuntu/jnpf-pg/ 或仓库根)。客户 ID 直接用【客户拼音】(2026-08-08 命名规范: # 容器名 cx-pg-<拼音>,一眼可辨客户;c2 是客户B的历史 ID,其容器名靠 CUSTOMER_NAME 对齐拼音): # ./postgres/tencent/add-customer.sh customer22 15434 # ./postgres/tencent/add-customer.sh tpl 15439 # # 凭据文件:每个客户【一份自己的】,用 ENV_FILE 指定(强烈建议,见下): # ENV_FILE=/home/ubuntu/jnpf-pg/.env.某客户 ./postgres/tencent/add-customer.sh customer22 15434 # 不指定则回退到共享的 .env。 # # ⚠️ 为什么要每客户一份:密码与连接配额是【按实例】不同的,而 create-app-roles.sh 会按凭据 # 文件【重置】角色密码。共用一份 .env 就只能"临时改 → 跑 → 记得改回来",漏改一次就会在 # 下次对别的实例跑本脚本时,把那个客户的密码改成这个客户的。ENV_FILE 让这类事故不可能发生。 # # 该文件需要的键(照 .env.example 抄,密码换成本客户独有的): # JNPF_PG_PASSWORD 超户密码 # JNPF_APP_DB_PASSWORD / JNPF_FLOW_DB_PASSWORD / JNPF_NACOS_DB_PASSWORD # JNPF_APP_CONN_LIMIT=120 / JNPF_FLOW_CONN_LIMIT=20 / JNPF_NACOS_CONN_LIMIT=5 # (对应本实例 max_connections=150,和 145 < 147) # # 另需:要从外网连时,云安全组已放行该端口(限来源 IP,别开 0.0.0.0/0)。 # # 本脚本【不做】灌产品基线——那要人判断洗哪些数据,见 ADR §8 第 6 步。 # ───────────────────────────────────────────────────────────────────────────── set -euo pipefail CUSTOMER_ID="${1:-}" CUSTOMER_PG_PORT="${2:-}" [ -n "$CUSTOMER_ID" ] && [ -n "$CUSTOMER_PG_PORT" ] || { echo "用法:add-customer.sh <客户ID,如 c2/c3/tpl> <端口,客户N=15431+N,母版=15439>" >&2 exit 1 } log() { printf '\033[1;34m[add-customer]\033[0m %s\n' "$*"; } die() { printf '\033[1;31m[add-customer][err]\033[0m %s\n' "$*" >&2; exit 1; } case "$CUSTOMER_ID" in ''|*[!a-z0-9]*) die "客户ID 只允许小写字母与数字(当前 '$CUSTOMER_ID'),它会成为容器名与卷名的一部分";; esac case "$CUSTOMER_PG_PORT" in ''|*[!0-9]*) die "端口必须是整数(当前 '$CUSTOMER_PG_PORT')";; esac SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PG_DIR="$(dirname "$SCRIPT_DIR")" # .../postgres CONTAINER="cx-pg-${CUSTOMER_ID}" # 护栏:绝不允许本脚本碰客户1 的既有实例(cx-postgres:15432,编排/内存/连接数都不同, # 且 create-app-roles.sh 会按当前 .env 重置角色密码——跑错实例就改错了客户1 的密码)。 # 判据用【端口】而不是容器名:容器名恒为 cx-pg-,永远不可能等于 cx-postgres,那种检查是死代码。 [ "$CUSTOMER_PG_PORT" = "15432" ] && die "15432 是客户1 既有实例 cx-postgres 的端口,本脚本不得操作它(新客户用 15431+N,母版用 15439)" if docker ps -a --format '{{.Names}}' 2>/dev/null | grep -qx "$CONTAINER"; then log "容器 ${CONTAINER} 已存在——按幂等继续(compose up 不会重建配置未变的容器)" fi for f in "$SCRIPT_DIR/docker-compose.customer-pg.yml" \ "$PG_DIR/migration/install.sh" \ "$PG_DIR/migration/create-app-roles.sh" \ "$SCRIPT_DIR/harden-pg-hba.sh"; do [ -f "$f" ] || die "缺 $f(先把仓库的 docker/postgres/ 整目录同步过来,见 ADR §8)" done # 凭据文件:ENV_FILE 优先,否则用共享 .env(compose 与建角色脚本必须读同一份, # 否则超户密码与业务角色密码会来自两个来源) if [ -n "${ENV_FILE:-}" ]; then [ -f "$ENV_FILE" ] || die "ENV_FILE 指定的 $ENV_FILE 不存在" ENV_PATH="$(cd "$(dirname "$ENV_FILE")" && pwd)/$(basename "$ENV_FILE")" else ENV_PATH=""; d="$SCRIPT_DIR" while [ "$d" != "/" ]; do [ -f "$d/.env" ] && { ENV_PATH="$d/.env"; break; } d="$(dirname "$d")" done [ -n "$ENV_PATH" ] || die "向上未找到 .env,且未指定 ENV_FILE" log "未指定 ENV_FILE,回退共享凭据文件 $ENV_PATH —— 多客户场景建议每客户一份,见文件头说明" fi log "凭据文件:$ENV_PATH" log "1/4 起容器 ${CONTAINER}(端口 ${CUSTOMER_PG_PORT},max_connections=150,shared_buffers=128MB)" CUSTOMER_ID="$CUSTOMER_ID" CUSTOMER_PG_PORT="$CUSTOMER_PG_PORT" \ docker compose --env-file "$ENV_PATH" \ -f "$SCRIPT_DIR/docker-compose.customer-pg.yml" up -d log "2/4 建四库 + schema(install.sh,库名与客户1 完全一致)" # 用 bash 显式调用,不依赖权限位——rsync/tar/zip 传播过程中执行位很容易丢 CONTAINER="$CONTAINER" bash "$PG_DIR/migration/install.sh" log "3/4 建角色 + 连接配额(create-app-roles.sh,读 ${ENV_PATH} 的密码与 *_CONN_LIMIT)" CONTAINER="$CONTAINER" ENV_FILE="$ENV_PATH" bash "$PG_DIR/migration/create-app-roles.sh" log "4/4 收紧 pg_hba(postgres 超户禁止公网登录)" CONTAINER="$CONTAINER" bash "$SCRIPT_DIR/harden-pg-hba.sh" cat <:${CUSTOMER_PG_PORT} 下一步: 1. 灌产品基线:起 cx-pg-template → DATABASES="jnpf_init jnpf_flow" promote-db.sh dump/restore (务必显式限定这两个库,默认值含 nacos_config,那是环境私有的,见 ADR §3.5) 2. 该客户各环境的 .env 填: CUSTOMER_DB_HOST= CUSTOMER_DB_PORT=${CUSTOMER_PG_PORT} 3. 本次用的凭据文件:${ENV_PATH} $(if [ -z "${ENV_FILE:-}" ]; then printf ' \033[1;33m⚠\033[0m 用的是共享 .env——若刚才为本客户改过密码/配额,现在必须还原成客户1 的值,\n 否则下次对 cx-postgres 跑建角色脚本会改错密码。改用 ENV_FILE=<每客户一份> 可根除这个风险。'; else printf ' \033[1;32m✓\033[0m 用的是该客户专属凭据文件,未触碰其他客户的 .env'; fi) EOF