Files
dsh_ai1net_server/归档/dsh-server-docs-微调-20260924/docs-sync-check.sh.bak
T
admin ce8e6ceed9 chore(工作区): 纳入版本控制基线(回收 411 MB 过程产物)
回收 411 MB(470 M → 58.8 M),全部经回收站,可恢复:
- 待清理/(146.2 M,含 relay 分片 128 M 与 42 项过程目录)
- tmp/(32.4 M,按接续棒命名的过程临时区)
- .workbuddy/tmp/(39.5 M)
- 4 份 workbuddy.db 冗余副本(101 M,09-23 事故的坏副本 / 抢救产物)
- tmp/im16/gw/centrifugo 二进制(63.9 M,可重下)+ 缓存残留

入库范围:常驻规则(CODEBUDDY.md / README.md / state.py)、在途接续入口与
接续包、docs/、交付物/、交接单/、归档/、scripts/、.codebuddy/、
.workbuddy/memory/;共 398 件,其中 >60 KB 的 26 件全为文档。

排除(.gitignore):tmp/、待清理/、运行态日志与缓存、*.db 与 DB 备份整目录、
打包二进制(*.tar.gz / *.tgz)、记忆修复前备份。
2026-09-24 07:51:03 +08:00

116 lines
5.2 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
# 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/", "05-交接单/.doing-", "05-交接单/.exec-lock")
SKIPD = (".git", "05-交接单/.doing-", "05-交接单/.exec-lock")
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 './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 './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"