Files
memoryweave/scripts/deploy.sh
T
小唯 de0ddf4b8b chore(deploy): bi-temporal e2e 闸门接进部署流程(堵『写了但永不开火』的洞)
背景:t_763e8701 交付了 bitemporal_e2e_test.go,但该测试默认 t.Skip
(需 ZHIYI_BITEMPORAL_SOCK 指向运行中的 sidecar)。没人设环境变量 ⇒
这道『机器闸门』永不开火 ⇒ 等于不存在(机制铁律:写完无人调用 = 零效力)。

改动:do_verify 增加 verify_e2e_bitemporal() ——
用本次已安装的 sidecar 开临时 socket + 临时 data-dir(不碰生产库),
跑 Go e2e(真实 LanceDB 写→读回),失败即 die。

起因事故:t_3ada332b 单测全 PASS 但生产 no-op —— 三列在 LanceDB 边界
被静默丢弃(Go payload 缺 3 键 + Rust schema 缺 3 列),
validityExpired 永远拿到空串 ⇒ 降权永不触发。本闸门即该事故的守门人。

验证(双向):
- 通过路径:deploy.sh --verify-only 全绿,含『✔ bi-temporal e2e 闸门通过』
- 失败路径:把测试指向不存在的 socket ⇒ go test 返回 FAIL(非零)⇒ 函数 rc≠0 即 die
2026-10-14 05:05:24 +08:00

157 lines
7.6 KiB
Bash
Executable File
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
# 织忆 MemoryWeave 统一部署脚本 — build two + install two + restart two + verify
# 2026-10-08 · t_f57b8bbf · 根治「重启 zhiyid 不重启 sidecar → Rust 改部署后 sidecar 跑旧二进制静默故障」事故
#
# 用法:
# ./scripts/deploy.sh 完整部署: 构建两+装两+停两+启两+校验
# ./scripts/deploy.sh --verify-only 只跑校验(不构建/不安装/不重启)—— 反例测试/人工体检用
# make deploy = ./scripts/deploy.sh
set -euo pipefail
REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
BIN_DIR="${BIN_DIR:-/home/muc/bin}"
# --allow-dirty:显式承担「把在途改动编进生产二进制」的风险(默认拒绝)
ALLOW_DIRTY=0
for _a in "$@"; do [ "$_a" = "--allow-dirty" ] && ALLOW_DIRTY=1; done
# 构建产物(持久路径,均已 gitignore)
BUILD_GO="$REPO_DIR/go/cmd/zhiyid/zhiyid"
BUILD_RUST="$REPO_DIR/rust/target/release/zhiyi-consolidate"
# 安装落点(与 ~/.config/systemd/user/*.service 的 ExecStart 一致)
INSTALL_GO="$BIN_DIR/zhiyid-new"
INSTALL_RUST="$BIN_DIR/zhiyi-consolidate"
# 服务名(用户级 systemd)
SVC_GO="zhiyid.service"
SVC_RUST="zhiyi-consolidate.service"
HEALTH_URL="${HEALTH_URL:-http://127.0.0.1:7821/health}"
log() { printf '\033[1;34m[deploy]\033[0m %s\n' "$*"; }
die() { printf '\033[1;31m[deploy:FAIL]\033[0m %s\n' "$*" >&2; exit 1; }
# 校验单个服务的运行态是否=本次构建的二进制
# 判据优先级(纪律): 副作用(PID/inode/校验和) > 返回值 > 自报
verify_service() {
local name="$1" build_path="$2" pid exe run_sha build_sha
# ① is-active
[ "$(systemctl --user is-active "$name")" = "active" ] || die "$name 非 active"
# ② 取 MainPID
pid="$(systemctl --user show -p MainPID --value "$name")"
[ -n "$pid" ] && [ "$pid" != "0" ] || die "$name: MainPID 为空/0"
# ③ 进程 exe 不得指向已删除 inode(旧二进制被 install 覆盖后、进程仍跑旧 inode → readlink 含 "(deleted)")
exe="$(readlink "/proc/$pid/exe" 2>/dev/null || true)"
case "$exe" in
"") die "$name: 无法 readlink /proc/$pid/exe" ;;
*" (deleted)"*) die "$name: 进程仍跑已删除 inode → 二进制被换过但进程没重启 ($exe)" ;;
esac
# ④ 运行中二进制校验和 == 本次构建产物(抓「磁盘装了新版但进程仍跑旧版」静默故障)
run_sha="$(sha256sum "/proc/$pid/exe" 2>/dev/null | awk '{print $1}')"
build_sha="$(sha256sum "$build_path" 2>/dev/null | awk '{print $1}')"
[ -n "$run_sha" ] || die "$name: 无法读取运行中二进制"
[ "$run_sha" = "$build_sha" ] || die "$name: 运行中二进制 ≠ 本次构建产物 (运行=$run_sha 构建=$build_sha) → sidecar 静默跑旧版"
log "✔ $name active · PID=$pid · exe=$exe · 校验和匹配本次构建"
}
# 深健康检查: /health 连 sidecar 可达性(P0 已做,故障返 503)→ 期望 200
verify_health() {
local code
code="$(curl -s -o /dev/null -w '%{http_code}' -m 5 "$HEALTH_URL")"
[ "$code" = "200" ] || die "/health 返回 $code (期望 200) → zhiyid 或 sidecar 故障"
log "✔ /health = $code"
}
# 🔴 构建前洁净检查(2026-10-14 加)。起因(当晚三次同族事故):
# ① c345ed5 把另一任务在途的 server.go 布线一起提交 ⇒ HEAD 直接编译不过
# ② 本脚本原先**直接从工作树构建** ⇒ 任一 worker 的在途改动会被顺手编进**生产二进制**,
# 而且部署后「运行二进制校验和匹配」会通过 —— 校验和只能证明「跑的是本次构建」,
# **不能证明「本次构建来自已提交的代码」**。这是验证链上的一个洞。
# 判据:`git status --porcelain`(含未跟踪,不含已忽略)必须为空。
# 未跟踪文件也要拦:`internal/peer` 那类事故正是「已提交代码引用了未跟踪文件」——
# 反之未跟踪的新源文件被编进去,二进制就与 commit 不一致。
preflight_clean_tree() {
if [ "$ALLOW_DIRTY" = "1" ]; then
log "⚠️ --allow-dirty:跳过洁净检查(本次构建含未提交改动,风险自负)"
return 0
fi
local dirty
dirty="$(git -C "$REPO_DIR" status --porcelain 2>/dev/null || true)"
if [ -n "$dirty" ]; then
printf '%s\n' "$dirty" | sed 's/^/ /' >&2
die "工作树不洁净 → 拒绝构建(会把在途改动编进生产二进制,部署后校验和仍会通过,无法事后察觉)。
处置:先提交/收工;确需带脏部署则显式加 --allow-dirty 并在卡上记录原因。"
fi
log "✔ 洁净检查通过(含未跟踪文件:无残留)"
}
do_build() {
preflight_clean_tree
log "构建 Go → $BUILD_GO"
( cd "$REPO_DIR/go" && CGO_ENABLED=1 go build -o cmd/zhiyid/zhiyid ./cmd/zhiyid )
log "构建 Rust → $BUILD_RUST"
( cd "$REPO_DIR/rust" && cargo build --release )
}
do_install() {
log "停止 $SVC_GO + $SVC_RUST"
systemctl --user stop "$SVC_GO" || true
systemctl --user stop "$SVC_RUST" || true
# 用 install 而非 cp:install 先写临时再 rename,避免 Text file busy(进程占着旧 inode 时 cp 会 EBUSY)
log "install 二进制(不用 cp,避免 Text file busy)"
install -m 0755 "$BUILD_GO" "$INSTALL_GO"
install -m 0755 "$BUILD_RUST" "$INSTALL_RUST"
log "daemon-reload"
systemctl --user daemon-reload
log "启动 $SVC_GO + $SVC_RUST"
systemctl --user start "$SVC_GO"
systemctl --user start "$SVC_RUST"
sleep 2
}
# bi-temporal e2e 闸门(2026-10-14 加):
# 为什么:`bitemporal_e2e_test.go` 默认 `t.Skip`(需 sidecar),
# 若无人设 ZHIYI_BITEMPORAL_SOCK ⇒ **闸门永不开火** ⇒ 等于不存在。
# 机制铁律「写完无人调用 = 零效力」⇒ 接进部署流程:每次部署后自动跑一遍。
# 跑法:用**本次已安装的** sidecar 开一个临时 socket + 临时 data-dir(不碰生产库),
# 跑 Go e2e(真实 LanceDB 写→读回)。失败即 die —— 这正是 2026-10-14
# 「只改 Go 不改 Rust ⇒ 三列在 LanceDB 边界静默丢 ⇒ 生产 no-op」的守门人。
verify_e2e_bitemporal() {
local sock=/tmp/mw-deploy-bit.sock data=/tmp/mw-deploy-bitdata log=/tmp/mw-deploy-bit.log spid rc
rm -rf "$data" "$sock"; mkdir -p "$data"
"$INSTALL_RUST" --mode socket --socket "$sock" --data-dir "$data" >"$log" 2>&1 &
spid=$!
for _ in $(seq 1 30); do [ -S "$sock" ] && break; sleep 0.5; done
if [ ! -S "$sock" ]; then
kill "$spid" 2>/dev/null
die "bi-temporal 闸门:临时 sidecar 未能监听 $sock(见 $log)"
fi
( cd "$REPO_DIR/go" && CGO_ENABLED=1 ZHIYI_BITEMPORAL_SOCK="$sock" \
go test -run TestBitemporalE2E ./internal/storage/ >/tmp/mw-deploy-bit-gotest.log 2>&1 )
rc=$?
kill "$spid" 2>/dev/null
for _ in $(seq 1 5); do kill -0 "$spid" 2>/dev/null || break; sleep 1; done
kill -0 "$spid" 2>/dev/null && kill -9 "$spid" 2>/dev/null
if [ "$rc" -ne 0 ]; then
sed 's/^/ /' /tmp/mw-deploy-bit-gotest.log | tail -15 >&2
die "bi-temporal 端到端闸门 FAIL(exit=$rc)—— 三列在 LanceDB 真实写→读回环断了。
典型根因:只改 Go payload 未改 Rust schema / 只改 Rust 未改 payload。"
fi
log "✔ bi-temporal e2e 闸门通过(三列经真实 LanceDB 写→读回 完整往返)"
}
do_verify() {
log "校验 ..."
verify_service "$SVC_GO" "$BUILD_GO"
verify_service "$SVC_RUST" "$BUILD_RUST"
verify_health
verify_e2e_bitemporal
log "✅ 部署成功: 两服务 active · 进程无 (deleted) · 运行二进制=本次构建 · /health=200 · e2e 闸门通过"
}
if [ "${1:-}" = "--verify-only" ]; then
do_verify
exit 0
fi
do_build
do_install
do_verify