feat(all): 数据库模块化拆分与版本化迁移、任务引擎命名体系收敛、物理输出校验加固与用户配置接通
- server/db: 拆 4929 行 db.rs 单体为 db/ 目录,migrations.rs 引入 PRAGMA user_version
版本化迁移运行器(M1~M13)
- 任务引擎 Phase 6/7b/7c 改名收敛:EngineStageConfig→PhaseConfig、StagePolicy→ResumePolicy、
Converged→Completed、删除 task_type 列、success_method 拆 tlusty_/synspec_ 双列、
新增 tlusty_status/synspec_status 半失败阶段守卫
- 科学正确性加固:conv_check 任意行 NaN/Inf/溢出判无效(0 行容忍)、新增 spec_is_valid
校验 SYNSPEC 脏谱、itek_history 逐次迭代全量保真、fmt_abn powf 溢出饱和
- 用户配置真正接通:tlusty_chain/tlusty_input 由死字段经 调度器→TaskSpec→executor→runner
透传生效;config 加载期 validate + deny_unknown_fields + 解析失败记 warn
- 调度修复:H1 活锁(pending_strategies 跳过已失败策略)、种子查找错误不再静默降级冷启动
- dashboard: 阶段配置面板 tlusty_stage/synspec_stage、"已完成"标签、迭代诊断展示
- docs: 新增 database_refactor_design.md,同步 database/api/PIPELINE/workflow_detail
This commit is contained in:
+123
-27
@@ -14,14 +14,17 @@
|
||||
# ./scripts/fetch_results.sh -d /backup/dcts-salvage # 覆盖备份根目录
|
||||
# ./scripts/fetch_results.sh --with-work # 连带拉取 data/work 沙盒残留
|
||||
# ./scripts/fetch_results.sh --include-local # 把本机节点也复制进备份树
|
||||
# ./scripts/fetch_results.sh --force # 忽略完成标记,强制重新同步
|
||||
#
|
||||
# 传输策略:双方均可用 rsync → 增量同步(断点续传、幂等);否则退化为 scp -r 全量
|
||||
# 拷贝(Windows 节点一般无 rsync,自动走 scp)。SSH 复用 deploy.sh 的 ControlMaster
|
||||
# 连接复用,多次执行不重复输密码。
|
||||
# 传输策略(两类节点都走增量,重复运行只补差异、不重拷全量):
|
||||
# - 双方均可用 rsync → rsync 增量(断点续传、幂等,自动补新增/变更文件)
|
||||
# - 否则(典型 Windows:无 rsync)→ scp 目录级增量:先列举远端顶层网格点子目录,
|
||||
# 只 scp 本地缺失的目录,已存在的不重拷(避免 win-01 的 52GB 全量重拷)
|
||||
# 远端 rsync/列目录探测均用多 shell 兼容命令(POSIX/command-v/where/Get-Command、
|
||||
# ls/dir/Get-ChildItem),适配 Linux + Windows(cmd/PowerShell 默认 shell)。
|
||||
# SSH 复用 deploy.sh 的 ControlMaster,多次执行不重复输密码。
|
||||
# ==============================================================================
|
||||
|
||||
set -eo pipefail
|
||||
set -euo pipefail
|
||||
|
||||
GREEN='\033[0;32m'
|
||||
BLUE='\033[0;34m'
|
||||
@@ -43,7 +46,6 @@ BACKUP_ROOT="${BACKUP_ROOT:-${WORK_DIR}/data/salvage}"
|
||||
ONLY_NODE=""
|
||||
WITH_WORK=false
|
||||
INCLUDE_LOCAL=false
|
||||
FORCE=false
|
||||
INTERACTIVE=false
|
||||
HAD_ARGS=false
|
||||
SELECTED_PROFILES=()
|
||||
@@ -59,7 +61,6 @@ while [[ $# -gt 0 ]]; do
|
||||
-i|--interactive) INTERACTIVE=true; shift ;;
|
||||
--with-work) WITH_WORK=true; shift ;;
|
||||
--include-local) INCLUDE_LOCAL=true; shift ;;
|
||||
--force) FORCE=true; shift ;;
|
||||
-h|--help)
|
||||
sed -n '2,32p' "$0"
|
||||
exit 0 ;;
|
||||
@@ -89,44 +90,132 @@ rsync_remote_path() { # $1 = 远端绝对路径
|
||||
esac
|
||||
}
|
||||
|
||||
# 传输一个目录树(rsync 优先,退化 scp)
|
||||
# 远端目录列表(仅顶层条目名,用于 scp 目录级增量比对)。
|
||||
# 按远端路径形态选择对应的列举命令:
|
||||
# - 盘符开头(如 E:/...) → Windows:powershell Get-ChildItem(兼容 cmd/PowerShell 默认 shell)
|
||||
# - 否则 → POSIX:ls -1
|
||||
# 返回名字列表(每行一个),失败返回空。
|
||||
remote_list_dir() { # $1=user $2=ip $3=port $4=ssh_opts $5=远端绝对目录
|
||||
local u="$1" ip="$2" port="$3" opts="$4" rdir="$5"
|
||||
# 统一 tr -d '\r':Windows powershell/openssh 输出带 CRLF,会让 grep -Fxq 精确匹配
|
||||
# 永远失败(已同步目录每次重传)并产生带 CR 的畸形 scp 路径。
|
||||
if [[ "${rdir}" =~ ^[A-Za-z]:[\\/] ]]; then
|
||||
# Windows 路径:经 powershell 列举,-Name 直接返回每项一行
|
||||
ssh -p "${port}" ${opts} "${u}@${ip}" \
|
||||
"powershell -NoProfile -Command \"Get-ChildItem -Name -Path '${rdir}'\"" 2>/dev/null | tr -d '\r'
|
||||
else
|
||||
ssh -p "${port}" ${opts} "${u}@${ip}" "ls -1 '${rdir}'" 2>/dev/null | tr -d '\r'
|
||||
fi
|
||||
}
|
||||
|
||||
# scp 单个远端目录到本地,采用"先落地 .partial 再原子 mv"模式:
|
||||
# 1. 目标目录 ${dest}/${d} 已存在 → 视为已同步,跳过(保持原增量语义)
|
||||
# 2. 否则 scp 远端目录到 ${dest}/${d}.partial
|
||||
# 3. scp 成功后 mv 为 ${dest}/${d}(同 dest 文件系统,原子语义)
|
||||
# 4. scp 失败 → 清理残留 .partial,return 非零,由调用方决定是否继续
|
||||
# 这样即便 scp 传输过程中断,也不会留下"伪完成"目录导致下次被跳过漏传。
|
||||
scp_dir() { # $1=u $2=ip $3=port $4=ssh_opts $5=远端根目录 $6=本地目标 $7=目录名
|
||||
local u="$1" ip="$2" port="$3" opts="$4" rdir="$5" dest="$6" d="$7"
|
||||
local final="${dest}/${d}"
|
||||
if [ -e "${final}" ]; then
|
||||
return 0
|
||||
fi
|
||||
local tmp="${dest}/.${d}.partial"
|
||||
# 兜底清理可能的历史残留(上次中断留下的半成品)
|
||||
rm -rf "${tmp}"
|
||||
if scp -P "${port}" ${opts} -r "${u}@${ip}:${rdir}/${d}" "${tmp}"; then
|
||||
mv "${tmp}" "${final}"
|
||||
else
|
||||
echo -e " ${RED}[!]${NC} scp 失败: ${d},清理残留 ${d}.partial"
|
||||
rm -rf "${tmp}"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 传输一个目录树(rsync 优先,退化 scp 目录级增量)
|
||||
sync_dir() { # $1=user $2=ip $3=port $4=远端绝对目录 $5=本地目标
|
||||
local u="$1" ip="$2" port="$3" rdir="$4" dest="$5"
|
||||
local ssh_opts="-o ControlMaster=auto -o ControlPath=${SSH_CONTROL_DIR}/cm-${u}@${ip}:${port} -o ControlPersist=1800"
|
||||
|
||||
mkdir -p "$(dirname "${dest}")"
|
||||
# 完成标记:已成功同步过且未 --force 时跳过
|
||||
if [ -f "${dest}/.fetch-complete" ] && [ "${FORCE}" != "true" ]; then
|
||||
echo -e " ${CYAN}[i]${NC} 已有完成标记 ${dest}/.fetch-complete,跳过(--force 可重拉)"
|
||||
return 0
|
||||
fi
|
||||
mkdir -p "${dest}"
|
||||
|
||||
# 连通性探测(BatchMode=no 允许交互输密码)。
|
||||
# 注意探测命令必须用 `echo ok` 而非 `true`:Windows OpenSSH 默认 shell 是
|
||||
# cmd/PowerShell,没有 Unix 的 true,会导致"密码正确但仍判连接失败"。
|
||||
# 探测命令用 `echo ok` 而非 `true`:Windows OpenSSH 默认 shell 是 cmd/PowerShell,
|
||||
# 没有 Unix 的 true,会导致"密码正确但仍判连接失败"。
|
||||
if ! ssh -p "${port}" -o ConnectTimeout=8 ${ssh_opts} "${u}@${ip}" "echo ok" >/dev/null 2>&1; then
|
||||
echo -e " ${RED}[!] 无法连接 ${u}@${ip},跳过本节点${NC}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# rsync 可用性(本机 + 远端)
|
||||
# rsync 可用性(本机 + 远端)。远端探测按平台分支:
|
||||
# Windows 路径 → powershell Get-Command;POSIX 路径 → command -v
|
||||
local rs_ok=false
|
||||
if command -v rsync >/dev/null 2>&1; then
|
||||
if ssh -p "${port}" ${ssh_opts} "${u}@${ip}" "command -v rsync" >/dev/null 2>&1; then
|
||||
local probe
|
||||
if [[ "${rdir}" =~ ^[A-Za-z]:[\\/] ]]; then
|
||||
probe="powershell -NoProfile -Command \"Get-Command rsync -ErrorAction SilentlyContinue | Out-Null\""
|
||||
else
|
||||
probe="command -v rsync"
|
||||
fi
|
||||
if ssh -p "${port}" ${ssh_opts} "${u}@${ip}" "${probe}" >/dev/null 2>&1; then
|
||||
rs_ok=true
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "${rs_ok}" = "true" ]; then
|
||||
# rsync 天然增量幂等:每次都跑,自动补齐新增/变更的文件。
|
||||
echo -e " ${BLUE}[→]${NC} rsync 增量同步: ${u}@${ip}:${rdir}/ → ${dest}/"
|
||||
rsync -a --partial --info=progress2 \
|
||||
-e "ssh -p ${port} ${ssh_opts}" \
|
||||
"${u}@${ip}:$(rsync_remote_path "${rdir}")/" "${dest}/"
|
||||
else
|
||||
echo -e " ${YELLOW}[→]${NC} 远端无 rsync,退化 scp 全量拷贝: ${u}@${ip}:${rdir}/ → ${dest}/"
|
||||
scp -P "${port}" ${ssh_opts} -r "${u}@${ip}:${rdir}/" "${dest}/"
|
||||
# 无 rsync(典型 Windows):scp 目录级增量——只拉本地缺失的网格点子目录,
|
||||
# 不重复拷贝已存在的目录(避免 win-01 的 52GB 全量重拷)。
|
||||
local remote_dirs existing missing
|
||||
remote_dirs=$(remote_list_dir "${u}" "${ip}" "${port}" "${ssh_opts}" "${rdir}")
|
||||
if [ -z "${remote_dirs}" ]; then
|
||||
echo -e " ${YELLOW}[!]${NC} 远端目录 ${rdir} 为空或无法列举,跳过"
|
||||
return 1
|
||||
fi
|
||||
existing=$(ls -1 "${dest}" 2>/dev/null)
|
||||
missing=""
|
||||
local d
|
||||
# 远端目录名白名单校验:scp 路径会把 ${d} 直接拼进 scp 的 remote 路径,
|
||||
# 这里只允许字母/数字/._-,禁止 / 与 . .. —— 防御性过滤畸形或注入目录名。
|
||||
while IFS= read -r d; do
|
||||
[ -n "$d" ] || continue
|
||||
case "$d" in
|
||||
"."|"..")
|
||||
echo -e " ${YELLOW}[!]${NC} 跳过可疑远端目录名: ${d}"
|
||||
continue ;;
|
||||
esac
|
||||
if [[ "$d" == */* ]] || ! [[ "$d" =~ ^[A-Za-z0-9._-]+$ ]]; then
|
||||
echo -e " ${YELLOW}[!]${NC} 跳过不符合白名单的远端目录名: ${d}"
|
||||
continue
|
||||
fi
|
||||
if ! grep -Fxq -- "$d" <<<"${existing}"; then
|
||||
missing="${missing}${d}"$'\n'
|
||||
fi
|
||||
done <<<"${remote_dirs}"
|
||||
|
||||
local total new
|
||||
total=$(printf '%s\n' "${remote_dirs}" | grep -c .)
|
||||
new=$(printf '%s' "${missing}" | grep -c .)
|
||||
echo -e " ${YELLOW}[→]${NC} scp 目录级增量: ${u}@${ip}:${rdir}/ 共 ${total} 个,缺失 ${new} 个 → ${dest}/"
|
||||
if [ "${new}" -eq 0 ]; then
|
||||
echo -e " ${GREEN}[√]${NC} 本地已是最新,无新增目录"
|
||||
return 0
|
||||
fi
|
||||
# scp 原子写入:先落地到 .partial 临时目录,成功后同文件系统原子 mv 为最终目录名。
|
||||
# 中途失败残留的 .partial 由下方 scp_dir 的 RETURN 时清理钩子统一兜底,
|
||||
# 避免下次因"目录已存在"误判为已同步而跳过、留下不完整子目录。
|
||||
trap 'rm -rf "${dest}"/.*.partial 2>/dev/null || true' RETURN
|
||||
while IFS= read -r d; do
|
||||
[ -n "$d" ] || continue
|
||||
echo -e " ${YELLOW}[→]${NC} scp 新增: ${d}"
|
||||
scp_dir "${u}" "${ip}" "${port}" "${ssh_opts}" "${rdir}" "${dest}" "$d"
|
||||
done <<<"${missing}"
|
||||
fi
|
||||
touch "${dest}/.fetch-complete"
|
||||
}
|
||||
|
||||
# 备份单个节点
|
||||
@@ -172,15 +261,18 @@ backup_node() { # $1 = profile 文件
|
||||
|
||||
local dest_rc="${BACKUP_ROOT}/${node_id}/result"
|
||||
if [ "${env_mode}" = "local" ]; then
|
||||
# 本机直接拷贝,不走网络
|
||||
# 本机节点:优先 rsync 增量(避免每次 cp -a 全量重拷),无 rsync 才退化 cp -a。
|
||||
echo -e " ${BLUE}[→]${NC} 本机复制: ./data/result → ${dest_rc}"
|
||||
mkdir -p "${dest_rc}"
|
||||
cp -a ./data/result/. "${dest_rc}/"
|
||||
touch "${dest_rc}/.fetch-complete"
|
||||
if command -v rsync >/dev/null 2>&1; then
|
||||
rsync -a --info=progress2 ./data/result/ "${dest_rc}/"
|
||||
else
|
||||
cp -a ./data/result/. "${dest_rc}/"
|
||||
fi
|
||||
else
|
||||
sync_dir "${u}" "${ip}" "${port}" "${dir}/data/result" "${dest_rc}" || return 1
|
||||
fi
|
||||
echo -e " ${GREEN}[√]${NC} result 已同步,共 $(ls "${dest_rc}" | grep -cv '^\.fetch-complete$' || true) 个网格点子目录"
|
||||
echo -e " ${GREEN}[√]${NC} result 已同步,共 $(ls -1 "${dest_rc}" 2>/dev/null | grep -c . || true) 个网格点子目录"
|
||||
|
||||
# 可选:一并拉取 data/work 沙盒残留(未清理的计算现场,含完整过程文件)
|
||||
if [ "${WITH_WORK}" = "true" ]; then
|
||||
@@ -188,9 +280,13 @@ backup_node() { # $1 = profile 文件
|
||||
if [ "${env_mode}" = "local" ]; then
|
||||
echo -e " ${BLUE}[→]${NC} 本机复制: ./data/work → ${dest_wk}"
|
||||
mkdir -p "${dest_wk}"
|
||||
cp -a ./data/work/. "${dest_wk}/" 2>/dev/null || true
|
||||
if command -v rsync >/dev/null 2>&1; then
|
||||
rsync -a ./data/work/ "${dest_wk}/" 2>/dev/null || true
|
||||
else
|
||||
cp -a ./data/work/. "${dest_wk}/" 2>/dev/null || true
|
||||
fi
|
||||
else
|
||||
sync_dir "${u}" "${ip}" "${port}" "${dir}/data/work" "${dest_wk}" || return 1
|
||||
sync_dir "${u}" "${ip}" "${port}" "${dir}/data/work" "${dest_wk}" || true
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user