#!/usr/bin/env bash # 治理库 → 云测试发布执行器:SSH 加密反向转发(在**本机控制面**上运行)。 # # 为什么不能写 localhost:5432:本机治理 PG 跑在 Docker 里,没有 5432 宿主机发布, # 也不允许为它开公网/主机端口。所以只能: # 1. 先解析 PG 容器在它自己网络里的真实 IP; # 2. 用 ssh -R 建反向转发,让云侧出现一个**只属于云主机/容器**的入口; # 3. 执行器通过这个入口连库,凭据只在本机与云端 0600 env 之间传递。 # # 两种模式(默认 streamlocal,不占 TCP 端口、不开监听): # streamlocal 云侧生成 unix socket(如 /run/kgtest-pg/.s.PGSQL.5432),执行器容器把该目录 # 挂进去,DSN 用 host=/run/kgtest-pg。**推荐**:没有 TCP 暴露, # 不受"容器看不到宿主机 127.0.0.1"影响。 # tcp 云侧 127.0.0.1:15433 loopback。⚠️ 只有云**宿主机**上的进程能用; # 容器里的进程看到的 127.0.0.1 是容器自己。要用必须在云侧再加一个 # 从 docker bridge 到 127.0.0.1 的桥接(socat/haproxy),那属于父运维决定, # 本脚本不替你开。 # # 用法: # MODE=streamlocal bash deploy/cloud-test/pg-forward.sh # MODE=tcp REMOTE_PORT=15433 bash deploy/cloud-test/pg-forward.sh set -euo pipefail SSH_HOST="${SSH_HOST:-weknora-test}" PG_CONTAINER="${PG_CONTAINER:-knowledge-governance-p1-postgres-1}" MODE="${MODE:-streamlocal}" REMOTE_SOCKET="${REMOTE_SOCKET:-/run/kgtest-pg/.s.PGSQL.5432}" REMOTE_PORT="${REMOTE_PORT:-15433}" die() { echo "pg-forward: $*" >&2; exit 1; } command -v docker >/dev/null || die "需要 docker(解析治理 PG 容器 IP)" docker inspect "$PG_CONTAINER" >/dev/null 2>&1 || die "找不到容器 $PG_CONTAINER" # 只取第一个网络里的 IP;多网络时打印提示,由人确认走哪个。 IPS=$(docker inspect -f '{{range $name, $net := .NetworkSettings.Networks}}{{$name}}={{$net.IPAddress}} {{end}}' "$PG_CONTAINER") PG_IP=$(echo "$IPS" | tr ' ' '\n' | sed -n 's/^[^=]*=\([0-9.]\+\)$/\1/p' | head -n1) [ -n "$PG_IP" ] || die "解析不到 $PG_CONTAINER 的容器 IP(网络:$IPS)" echo "pg-forward: 治理 PG 容器 $PG_CONTAINER -> $PG_IP:5432(网络:$IPS)" case "$MODE" in streamlocal) [[ "$REMOTE_SOCKET" =~ ^/[A-Za-z0-9_/-]+/\.s\.PGSQL\.5432$ ]] || die "REMOTE_SOCKET 必须是绝对目录下的 .s.PGSQL.5432" ssh -o BatchMode=yes -o ExitOnForwardFailure=yes -o ServerAliveInterval=15 -o ServerAliveCountMax=3 \ "$SSH_HOST" "install -d -m 0700 -o root -g root $(dirname "$REMOTE_SOCKET")" DSN_SHAPE="postgres://@/?host=$(dirname "$REMOTE_SOCKET")&port=5432&sslmode=disable" echo "pg-forward: 云侧入口 unix socket = $REMOTE_SOCKET(仅云主机与挂载它的容器可见)" echo "pg-forward: 执行器容器必须挂载 -v $(dirname "$REMOTE_SOCKET"):$(dirname "$REMOTE_SOCKET")" echo "pg-forward: DATABASE_URL 形状 = $DSN_SHAPE" exec ssh -N -o BatchMode=yes -o ExitOnForwardFailure=yes \ -o ServerAliveInterval=15 -o ServerAliveCountMax=3 \ -R "$REMOTE_SOCKET:$PG_IP:5432" "$SSH_HOST" ;; tcp) echo "pg-forward: 云侧 127.0.0.1:$REMOTE_PORT -> 本机 $PG_IP:5432(仅云宿主机可用)" echo "pg-forward: 容器内进程不能用这个 loopback;要么用 MODE=streamlocal,要么另做 bridge(父决定)" exec ssh -N -o BatchMode=yes -o ExitOnForwardFailure=yes \ -o ServerAliveInterval=15 -o ServerAliveCountMax=3 \ -R "127.0.0.1:$REMOTE_PORT:$PG_IP:5432" "$SSH_HOST" ;; *) die "MODE 只能是 streamlocal 或 tcp(当前 $MODE)" ;; esac