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

117 lines
5.3 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
# docs-sync-check.sh — 对账「本机文档镜像」与「服务器文档库」
#
# 用法:
# bash 07-scripts/docs-sync-check.sh # 报告双端差异
# DOCS_REMOTE=bt-server bash 07-scripts/docs-sync-check.sh
#
# 可覆盖的环境变量:
# DOCS_LOCAL_DIR 本机镜像目录(默认 D:/github/dsh_shenxian/dsh-server-docs)
# DOCS_REMOTE ssh 别名或 host(默认 bt-server)
# DOCS_REMOTE_DIR 服务器文档目录(默认 /opt/dsh/docs)
#
# 退出码:0 = 完全一致;1 = 存在差异;2 = 环境/连接错误
set -uo pipefail
LOCAL_DIR="${DOCS_LOCAL_DIR:-D:/github/dsh_shenxian/dsh-server-docs}"
REMOTE="${DOCS_REMOTE:-bt-server}"
REMOTE_DIR="${DOCS_REMOTE_DIR:-/opt/dsh/docs}"
[ -d "$LOCAL_DIR" ] || { echo "ERROR: 本机目录不存在: $LOCAL_DIR" >&2; exit 2; }
tmp_l="$(mktemp)"; tmp_r="$(mktemp)"
trap 'rm -f "$tmp_l" "$tmp_r" 2>/dev/null || true' EXIT
# ── 本机哈希:优先「一次 Python 算完」───────────────────────────────
# 历史坑(2026-09-12 实测):Git Bash 下 `find | while read` **逐文件 spawn** `md5sum` / `cut`
# (132 文件 ≈ 264 次进程启动)→ 全量对账 **2 分 15 秒**,慢到被调用方(handoff-guard 信息模式)当成"挂死"。
# Python 一次遍历 → 秒级。探测顺序:$DSH_PY → python3 → python → 本机兜底绝对路径。
PY="${DSH_PY:-}"
if [ -z "$PY" ]; then
for c in python3 python; do
if command -v "$c" >/dev/null 2>&1; then PY="$c"; break; fi
done
fi
if [ -z "$PY" ] && [ -x "E:/ProgramData/.workbuddy/binaries/python/versions/3.13.12/python.exe" ]; then
PY="E:/ProgramData/.workbuddy/binaries/python/versions/3.13.12/python.exe"
fi
hash_local() {
if [ -n "$PY" ]; then
( cd "$LOCAL_DIR" && "$PY" -c '
import os, sys, hashlib
try:
sys.stdout.reconfigure(encoding="utf-8", newline="\n")
except Exception:
pass
SKIPF = (".git/", "tmp/", "05-交接单/.doing-", "05-交接单/.exec-lock", "05-交接单/.locks/")
SKIPD = (".git", "tmp", "05-交接单/.doing-", "05-交接单/.exec-lock", "05-交接单/.locks")
rows = []
for root, dirs, files in os.walk("."):
# ⚠️ 不能用 os.path.relpath 拼 rel —— Windows 上它会把「末尾带点」的名字规范化掉
# (实测:`INDEX.md.bak-….` 变成无点的名字 → open 失败 → 该文件被静默漏掉 → 制造假差异)。
r = root.replace(os.sep, "/")
pfx = "" if r in (".", "") else (r[2:] if r.startswith("./") else r)
dirs[:] = [d for d in dirs if not (((pfx + "/") if pfx else "") + d).startswith(SKIPD)]
for fn in files:
rel = (pfx + "/" + fn) if pfx else fn
if fn == ".DS_Store" or any(rel.startswith(p) for p in SKIPF):
continue
if os.path.islink(rel):
continue # 与 find -type f 同口径
try:
with open(rel, "rb") as fh:
h = hashlib.md5(fh.read()).hexdigest()
except Exception:
# Windows 会把「末尾带点 / 保留名」等非常规文件名规范化掉 → open 直接 ENOENT。
# 用 \\?\ 前缀绕过,**必须拼未规范化的绝对路径**(os.path.abspath 会 strip 末尾点 → 前缀失效)。
try:
with open("\\\\?\\" + os.getcwd() + "\\" + rel.replace("/", "\\"), "rb") as fh:
h = hashlib.md5(fh.read()).hexdigest()
except Exception:
continue
rows.append((rel, h))
rows.sort(key=lambda r: r[0].encode("utf-8")) # 与 LC_ALL=C sort 同口径
for rel, h in rows:
print(rel + "\t" + h)
' )
else
echo "WARN: 未找到 python,回退逐文件 md5sum(会慢 ~2 分钟)" >&2
( cd "$LOCAL_DIR" && find . -type f ! -path './.git/*' ! -name '.DS_Store' \
! -path './tmp/*' ! -path '*/.locks/*' \
! -path './05-交接单/.doing-*' ! -path './05-交接单/.exec-lock*' | LC_ALL=C sort | while read -r f; do
printf '%s\t%s\n' "${f#./}" "$(md5sum "$f" | cut -d' ' -f1)"
done )
fi
}
hash_remote() {
ssh -o BatchMode=yes -o ConnectTimeout=10 "$REMOTE" \
"cd '$REMOTE_DIR' 2>/dev/null && find . -type f ! -path './.git/*' ! -name '.DS_Store' ! -path './tmp/*' ! -path '*/.locks/*' ! -path './05-交接单/.doing-*' ! -path './05-交接单/.exec-lock*' | LC_ALL=C sort | while read -r f; do printf '%s\t%s\n' \"\${f#./}\" \"\$(md5sum \"\$f\" | cut -d' ' -f1)\"; done" \
2>/dev/null
}
hash_local > "$tmp_l"
hash_remote > "$tmp_r"
if [ ! -s "$tmp_r" ]; then
echo "ERROR: 无法读取服务器目录 $REMOTE:$REMOTE_DIR(ssh 失败或目录不存在)" >&2
exit 2
fi
awk -F'\t' '
NR==FNR { l[$1]=$2; next }
{ r[$1]=$2 }
END {
same=0
for (k in l) {
if (k in r) { if (l[k]==r[k]) same++; else { diff++; print "⚠️ 内容不一致 " k } }
else { only_l++; print "⬆️ 仅本地(待推送) " k }
}
for (k in r) if (!(k in l)) { only_r++; print "⬇️ 仅服务器(待拉取) " k }
printf "\n—— 汇总 ——\n 一致: %d\n 内容不一致: %d\n 仅本地: %d\n 仅服务器: %d\n 本地文件总数: %d / 服务器文件总数: %d\n", same, diff+0, only_l+0, only_r+0, length(l), length(r)
if ((diff+0)+(only_l+0)+(only_r+0) > 0) { print "\n结果: 存在差异 ❌"; exit 1 }
print "\n结果: 双端一致 ✅"
}
' "$tmp_l" "$tmp_r"