#!/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-<id>,永远不可能等于 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 <<EOF
|
|
$(printf '\033[1;32m✓\033[0m') ${CONTAINER} 就绪。坐标 = <CVM公网IP>:${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=<CVM公网IP>
|
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
|