103 lines
5.4 KiB
Bash
103 lines
5.4 KiB
Bash
#!/usr/bin/env bash
|
||||
|
|
# ==============================================================================
|
|||
|
|
# 🔴 2026-09-23 已作废,⛔ 不得执行。
|
|||
|
|
# 原因:PG 显式禁止从函数内执行 CREATE DATABASE(SECURITY DEFINER 路径无效)。
|
|||
|
|
# 替代件:交付物/建库权限-D档被证伪与修正判定-20260923.md
|
|||
|
|
# ==============================================================================
|
|||
|
|
# D 档一次性落地:建库函数 + 7 条验收 + 锁管理(幂等 · 可重复跑)
|
|||
# 用法: bash "E:/ProgramData/AIProject/aliyun-dsh-server/交付物/插件建库权限-一次性执行.sh"
|
||||
# 设计目标:**把占锁窗口压到 ~40 秒**(抢不到就立刻退出,不空转)
|
||||
|
|
# 退出码: 0=全绿 2=DDL/验收失败 3=抢锁失败(别的会话在跑)
|
|||
|
|
# ⚠️ 本脚本会 `su postgres` 建 SECURITY DEFINER 函数 —— 已由用户 2026-09-22 19:3x 拍板 D
|
|||
|
|
# ==============================================================================
|
|||
|
|
set -u
|
|||
|
|
GUARD="D:/github/dsh_shenxian/dsh-server-docs/scripts/handoff-guard.sh"
|
|||
|
|
HOST="[email protected]"
|
|||
|
|
ME="插件投放与分库线-执行棒(抢间隙)"
|
|||
|
|
|
|||
|
|
# ── 1 抢全局执行锁(抢不到立刻走人)─────────────────────────────────────────
|
|||
|
|
CLAIM_OUT="$(bash "$GUARD" --claim-exec "$ME" 2>&1)"
|
|||
|
|
if printf '%s' "$CLAIM_OUT" | grep -q "✗"; then
|
|||
|
|
echo "LOCK_BUSY :: $(printf '%s' "$CLAIM_OUT" | grep -m1 '占用者' | cut -c1-80)"
|
|||
|
|
echo "→ 停手(R9:不得删锁 / 不得接管)。过一会儿再跑本脚本即可。"
|
|||
|
|
exit 3
|
|||
|
|
fi
|
|||
|
|
echo "LOCK_HELD=yes"
|
|||
|
|
# 无论成败都放锁(这是抢间隙策略的前提)
|
|||
|
|
trap 'bash "$GUARD" --release-exec >/dev/null 2>&1; echo "LOCK_RELEASED=yes"' EXIT
|
|||
|
|
|
|||
|
|
# ── 2 落地 DDL(幂等)────────────────────────────────────────────────────────
|
|||
|
|
echo "--- 2 落地 DDL ---"
|
|||
|
|
ssh -o ConnectTimeout=8 -o BatchMode=yes "$HOST" 'bash -s' <<'REMOTE'
|
|||
|
|
set -u
|
|||
|
|
cat > /tmp/.pdl-d.sql <<'SQL'
|
|||
|
|
CREATE SCHEMA IF NOT EXISTS dshs_int AUTHORIZATION postgres;
|
|||
|
|
REVOKE ALL ON SCHEMA dshs_int FROM PUBLIC;
|
|||
|
|
GRANT USAGE ON SCHEMA dshs_int TO dshs;
|
|||
|
|
|
|||
|
|
CREATE OR REPLACE FUNCTION dshs_int.create_plugin_database(p_name text)
|
|||
|
|
RETURNS text
|
|||
|
|
LANGUAGE plpgsql
|
|||
|
|
SECURITY DEFINER
|
|||
|
|
SET search_path = pg_catalog
|
|||
|
|
AS $fn$
|
|||
|
|
DECLARE
|
|||
|
|
v_owner constant text := 'dshs';
|
|||
|
|
BEGIN
|
|||
|
|
IF p_name IS NULL OR p_name !~ '^dshs_pl_[a-z][a-z0-9_]{0,40}$' THEN
|
|||
|
|
RAISE EXCEPTION 'invalid plugin database name: %', p_name USING ERRCODE = '22023';
|
|||
|
|
END IF;
|
|||
|
|
IF EXISTS (SELECT 1 FROM pg_database WHERE datname = p_name) THEN
|
|||
|
|
RETURN 'exists';
|
|||
|
|
END IF;
|
|||
|
|
EXECUTE format('CREATE DATABASE %I OWNER %I ENCODING %L TEMPLATE template0',
|
|||
|
|
p_name, v_owner, 'UTF8');
|
|||
|
|
RAISE LOG 'dshs plugin db created: %', p_name;
|
|||
|
|
RETURN 'created';
|
|||
|
|
EXCEPTION
|
|||
|
|
WHEN duplicate_database THEN
|
|||
|
|
RETURN 'exists';
|
|||
|
|
END;
|
|||
|
|
$fn$;
|
|||
|
|
|
|||
|
|
REVOKE ALL ON FUNCTION dshs_int.create_plugin_database(text) FROM PUBLIC;
|
|||
|
|
GRANT EXECUTE ON FUNCTION dshs_int.create_plugin_database(text) TO dshs;
|
|||
|
|
SQL
|
|||
|
|
su postgres -c 'cd /tmp && psql -h /var/run/postgresql -p 15432 -d dshs -X -v ON_ERROR_STOP=1 -q -f /tmp/.pdl-d.sql' 2>&1 | head -10
|
|||
|
|
echo "DDL_RC=$?"
|
|||
|
|
rm -f /tmp/.pdl-d.sql
|
|||
|
|
REMOTE
|
|||
|
|
[ $? -ne 0 ] && { echo "DDL_FAILED"; exit 2; }
|
|||
|
|
|
|||
|
|
# ── 3 验收 7 条 ──────────────────────────────────────────────────────────────
|
|||
|
|
echo "--- 3 验收 ---"
|
|||
|
|
ssh -o ConnectTimeout=8 -o BatchMode=yes "$HOST" 'bash -s' <<'REMOTE'
|
|||
|
|
P() { su postgres -c "cd /tmp && psql -h /var/run/postgresql -p 15432 -d dshs -X -A -t -q -c \"$1\"" 2>&1 | head -2; }
|
|||
|
|
U() { python3 - <<'PY'
|
|||
|
|
import glob,re,os
|
|||
|
|
url=""
|
|||
|
|
for f in ["/etc/dshs.env"]+glob.glob("/etc/systemd/system/dshs.service.d/*.conf"):
|
|||
|
|
try:
|
|||
|
|
for line in open(f,encoding="utf-8",errors="replace"):
|
|||
|
|
m=re.search(r'DSHS_DB_URL\s*=\s*"?([^"\s]+)"?',line)
|
|||
|
|
if m: url=m.group(1)
|
|||
|
|
except Exception: pass
|
|||
|
|
import urllib.parse as up
|
|||
|
|
print(up.urlparse(url).password or "")
|
|||
|
|
PY
|
|||
|
|
}
|
|||
|
|
export PGPASSWORD="$(U)"
|
|||
|
|
D() { psql -h 127.0.0.1 -p 15432 -U dshs -d dshs -X -A -t -q -c "$1" 2>&1 | head -2; }
|
|||
|
|
|
|||
|
|
echo "① create = $(D "select dshs_int.create_plugin_database('dshs_pl_selftest')")"
|
|||
|
|
echo "② 幂等 = $(D "select dshs_int.create_plugin_database('dshs_pl_selftest')")"
|
|||
|
|
echo "③ 库与属主 = $(P "select datname||'/'||pg_get_userbyid(datdba) from pg_database where datname='dshs_pl_selftest'")"
|
|||
|
|
echo "④ 白名单(evil) = $(D "select dshs_int.create_plugin_database('evil')" | head -1)"
|
|||
|
|
echo "④' 注入串 = $(D "select dshs_int.create_plugin_database('dshs_pl_x; DROP DATABASE dshs')" | head -1)"
|
|||
|
|
echo "⑤ dshs 权限(须 f|f) = $(P "select rolsuper||'|'||rolcreatedb from pg_roles where rolname='dshs'")"
|
|||
|
|
echo "⑥ 无旁路 = $(D 'create database pdl_should_fail' | head -1)"
|
|||
|
|
echo "⑦ 可连新库+建schema = $(PGPASSWORD="$PGPASSWORD" psql -h 127.0.0.1 -p 15432 -U dshs -d dshs_pl_selftest -X -A -t -q -c 'create schema s_test' 2>&1 | head -1)"
|
|||
|
|
echo "→ 清理 = $(P 'DROP DATABASE dshs_pl_selftest' >/dev/null 2>&1 && echo dropped)"
|
|||
|
|
echo "→ 残留库数(须0) = $(P "select count(*) from pg_database where datname like 'dshs_pl_%'")"
|
|||
|
|
REMOTE
|
|||
|
|
echo "DONE"
|