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 一律写「远程服务器」。
97 lines
4.6 KiB
Bash
97 lines
4.6 KiB
Bash
#!/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
|