Files
admin e6207aa691
build / build-and-scan (push) Waiting to run
chore(仓库对齐): 文档库结构治理 + IM/插件线落地
文档库:目录改为编号制(01-规范/02-架构设计/03-数据库/04-调整方案/
05-交接单/06-ops/07-scripts/08-skills/09-archive),顶层散文件归入 01-规范/;
INDEX.md 与 docs-manifest.json 重刷(档案 146 篇);旧目录名引用全量对齐。

IM 线:src/im/**(SDK / hub / store / presence / ws / gateway-token)、
src/web/routes/im.ts、src/db/plugin-data/**、src/supervisor/plugin-assembly.ts
及对应 test/**。

插件线:poc/{im-agent-bridge,im-connection-gateway,im-conversation-tabs,
business-plugins-im,carbon-mcp-probe}、src/web/routes/{sessions,overlay-device}.ts、
src/net/relay/{device-grant,instance-credential}.ts。

仓库卫生:清出 40 个历史误入库 / 已改名文件(34 个交接单归档 + 6 个旧结构,
本地均有副本);dsh-server-docs/.gitignore 补 tmp/;交接单不入库(政策)。
2026-09-24 07:25:16 +08:00

97 lines
4.6 KiB
Bash
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# op-lock.sh — 「平台高危操作锁」(服务器侧第二把锁)
#
# 为什么需要它:所有会话**共用一台服务器**,而下面这些操作跨会话没有任何互斥 ——
# 重启 dshs / drain 实例 scope / 改实例 env·MemoryMax / 批量铺插件 /
# 改 nginx vhost·证书·nft
# 文档冲突靠 git status / mtime 能看出来,**服务器态变更看不出来**
# (systemctl 不会告诉你 10 分钟前谁重启过)→ 只能靠显式锁。
#
# 与文档库锁的关系:`05-交接单/.doing-<单>` + `.exec-lock` 管「文档与单子」;本锁管「生产态」。
# 顺序:**先抢全局执行锁 → 再占本锁**。
#
# 用法:
# bash 07-scripts/op-lock.sh claim <操作名> "<影响面/时长>" [占用者]
# bash 07-scripts/op-lock.sh release <操作名>
# bash 07-scripts/op-lock.sh status
#
# 判据:`mkdir` 原子 —— 成功 = 你拿到;报 File exists = 有别的会话正在动线上 → **停手**。
# 退出码:0 = 成功/无人占用;1 = 已被占用(claim 失败)或环境错误。
set -uo pipefail
REMOTE="${OP_LOCK_REMOTE:-bt-server}"
LOCKROOT="${OP_LOCK_DIR:-/opt/dsh/state/.op-lock}"
OP="${2:-}"
WHO="${4:-${ME:-unknown-session}}"
NOW="$(date '+%Y-%m-%d %H:%M')"
usage() {
sed -n '3,20p' "$0" | sed 's/^# \{0,1\}//'
exit 2
}
case "${1:-}" in
claim)
[ -n "$OP" ] || usage
IMPACT="${3:-(未声明影响面 —— 补上:谁会被断、断多久)}"
if ssh -o BatchMode=yes -o ConnectTimeout=10 "$REMOTE" "mkdir '$LOCKROOT/$OP' 2>/dev/null"; then
ssh -o BatchMode=yes -o ConnectTimeout=10 "$REMOTE" \
"printf '占用者:%s\n开始时间:%s\n操作摘要:%s\n影响面:%s\n' '$WHO' '$NOW' '$OP' '$IMPACT' > '$LOCKROOT/$OP/OWNER'" || true
echo "✅ 已占位:$LOCKROOT/$OP(占用者 $WHO,$NOW)"
if [ "$WHO" = "unknown-session" ]; then
echo " ⚠ 未提供会话名 → 已记作 unknown-session。后果有两个(2026-09-12 实测踩到):"
echo " ① handoff-guard 的【1d】会把它当成**别人的锁**(连你自己都被挡住);"
echo " ② release 的归属校验会拒绝你。请改用:"
echo " ME=\"<会话名>\" bash 07-scripts/op-lock.sh claim <操作名> \"<影响面>\""
fi
echo " 完工请:bash 07-scripts/op-lock.sh release $OP"
exit 0
fi
echo "❌ 占位失败 —— 该锁已存在(有会话正在动线上)→ 停手。" >&2
bash "$0" status >&2 || true
exit 1
;;
release)
[ -n "$OP" ] || usage
# 归属校验(R9,2026-09-12 补):锁只能由**持有者自己**释放 ——
# 否则 release 就成了绕过 R9 的后门(AI 读一下 status 拿到操作名,就能把别人的锁 rm 掉)。
RWHO="${4:-${ME:-unknown-session}}"
ROWN="$(ssh -o BatchMode=yes -o ConnectTimeout=10 "$REMOTE" \
"sed -n '1p' '$LOCKROOT/$OP/OWNER' 2>/dev/null" 2>/dev/null | sed 's/^占用者://')"
if [ -z "$ROWN" ]; then
echo "❌ 拒绝释放:「$OP」没有 OWNER 记录(无法确认归属)。" >&2
echo " 按 **R9**:锁的处置权只属于用户本人 —— 请报告用户,由用户处置。" >&2
exit 1
fi
if [ "$ROWN" != "$RWHO" ] && [ "${FORCE:-0}" != "1" ]; then
echo "❌ 拒绝释放:「$OP」的占用者是「$ROWN」,而你是「$RWHO」。" >&2
echo " 按 **R9**(禁止接管 / 删锁):抢不到锁 = 停手 + 报告用户;**锁的处置权只属于用户本人**。" >&2
echo " (确需处置:由**用户本人**执行;或用户明确点头后用 FORCE=1,并在回报里写明理由。)" >&2
exit 1
fi
ssh -o BatchMode=yes -o ConnectTimeout=10 "$REMOTE" "rm -rf '$LOCKROOT/$OP'" || { echo "❌ 释放失败(ssh 不可达?)" >&2; exit 1; }
echo "✅ 已释放:$OP(归属已核:$ROWN)"
;;
status)
echo "—— 平台操作锁($REMOTE:$LOCKROOT)——"
OUT="$(ssh -o BatchMode=yes -o ConnectTimeout=10 "$REMOTE" \
"ls -1 '$LOCKROOT' 2>/dev/null | grep -v '^README$'" 2>/dev/null)"
if [ -z "$OUT" ]; then
if ssh -o BatchMode=yes -o ConnectTimeout=10 "$REMOTE" "test -d '$LOCKROOT'" 2>/dev/null; then
echo " (无人占用 ✓)"
else
echo " ⚠ 锁根目录不存在:$LOCKROOT(需先建立)" >&2
exit 1
fi
else
for L in $OUT; do
echo " 🔴 $L"
ssh -o BatchMode=yes -o ConnectTimeout=10 "$REMOTE" \
"sed 's/^/ /' '$LOCKROOT/$L/OWNER' 2>/dev/null" || true
done
exit 1
fi
;;
*) usage ;;
esac