Files
dsh_shenxian/dsh-server-docs/scripts/op-lock.sh
T
admin 5ad755116e chore(docs): 文档库并入代码仓(R4 选 a)+ 索引/台账跟进
1) dsh-server-docs/ 从工作区(原 E:\...\aliyun-dsh-server\dsh-server-docs)**整体并入本仓**,
   保留目录名 ⇒ 仓库内 dsh-server-docs/... 的相对引用天然继续有效;旧目录(含其 .git)已归档到
   工作区 _中间产物_待清理/,未随本提交带入。
2) .gitattributes:新增 `dsh-server-docs/** -text` —— 原文档库是 `* -text` + autocrlf=false,
   必须保持纯 LF,否则会被本仓的 CRLF 规则翻掉。
3) 活引用里的绝对路径已全部改到新位置(docs 的 INDEX / README / scripts / skills + 用户级 skills
   + ~/.workbuddy/settings.json 的 hooks);历史档案(04-调整方案/、archive/)按「只增不改」未动。
   ⚠️ hooks 路径改动需「完全重启会话」才生效(配置是会话启动快照)。
4) 交接单/T08:新增 §16「生产整体切换执行记录」(形态 / 落地动作 / **4 个只有真上线才暴露的真 bug** /
   验收证据 / 回滚命令 / 残留项);台账 T08 行 → 已完成并归档;03-路线图 §二 登记 T08 收尾项。
5) 统一称谓:**「本机」只指跑 WorkBuddy 的开发机**,47 / 106 一律写「远程服务器」。
2026-09-15 18:47:13 +08:00

97 lines
4.6 KiB
Bash
Raw 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 分钟前谁重启过)→ 只能靠显式锁。
#
# 与文档库锁的关系:`交接单/.doing-<单>` + `.exec-lock` 管「文档与单子」;本锁管「生产态」。
# 顺序:**先抢全局执行锁 → 再占本锁**。
#
# 用法:
# bash scripts/op-lock.sh claim <操作名> "<影响面/时长>" [占用者]
# bash scripts/op-lock.sh release <操作名>
# bash 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 scripts/op-lock.sh claim <操作名> \"<影响面>\""
fi
echo " 完工请:bash 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