xiaowei-system/scripts/fix-state-db-restore.sh

139 lines
5.7 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.

#!/bin/bash
# fix-state-db-restore.sh — state.db 最终修复2026-09-03 深夜)
# 方案19:26 healthy 备份为基底 + messages/system_prompts 增量补回 = 零丢失
# 背景live state.db 所有表数据可读,但 messages 表 b-tree 内部节点 page 63262
# 双重引用VACUUM/REINDEX/FTS rebuild 均无法修复,官方 .recover 缺 dbpage
# sessions 两库完全一致(365)messages 差 285 条(id>100564)system_prompts 差 1 条
# 用法:从 gateway 外部/后台 shell 跑;脚本自管 gateway 启停
set -uo pipefail
DB=/home/muc/.hermes/state.db
BASE=/home/muc/.hermes/state.db.before-fix-deploy-20260903_192633 # healthy 基底
TS=$(date +%Y%m%d_%H%M%S)
LOG=/tmp/fix-state-db-restore-$TS.log
TMP=/tmp/restored-$TS.db
PROG=/tmp/state-db-fix-progress.md
log() { echo "[$(date '+%H:%M:%S')] $*" | tee -a "$LOG"; }
log "=== state.db 最终修复开始 (TS=$TS) ==="
# 0. 等 8 秒,确保发起方的消息已发出(本脚本可能由 gateway 内 agent 触发)
sleep 8
# 1. 停 gateway + 防 auto-restart
GW=$(systemctl --user show hermes-gateway -p MainPID --value 2>/dev/null)
if [ -n "$GW" ] && [ "$GW" != "0" ]; then
log "停止 gateway (PID $GW)..."
systemctl --user stop hermes-gateway
sleep 5
systemctl --user reset-failed hermes-gateway 2>/dev/null
fi
ACT=$(systemctl --user is-active hermes-gateway 2>/dev/null)
log "gateway 状态: $ACT"
if [ "$ACT" = "active" ] || [ "$ACT" = "activating" ]; then
log "❌ gateway 仍在跑/被拉起,中止修复避免抢 DB"
exit 1
fi
# 2. 备份 live修复前最新状态含 gateway 停前最后写入)
log "备份 live → $DB.pre-restore-$TS"
cp "$DB" "$DB.pre-restore-$TS"
log " 备份: $(du -h "$DB.pre-restore-$TS" | cut -f1)"
# 3. 从健康基底重建恢复库 + 增量补回
log "$BASE 重建恢复库 + 增量补回..."
cp "$BASE" "$TMP" || { log "❌ cp 基底失败"; exit 1; }
/usr/bin/python3 - "$TMP" "$DB" <<'PYEOF' 2>>"$LOG" || { log "❌ 增量补回失败"; exit 1; }
import sqlite3, sys
dst_path, src_path = sys.argv[1], sys.argv[2]
src = sqlite3.connect(f'file:{src_path}?mode=ro', uri=True, timeout=120)
dst = sqlite3.connect(dst_path, timeout=120)
src.execute("PRAGMA busy_timeout=60000"); dst.execute("PRAGMA busy_timeout=60000")
# 1) messages 增量id > 基底 max
max_id_dst = dst.execute("SELECT COALESCE(MAX(id),0) FROM messages").fetchone()[0]
cols = [d[0] for d in src.execute("SELECT * FROM messages LIMIT 1").description]
ph = ",".join("?"*len(cols))
rows = src.execute(f"SELECT * FROM messages WHERE id > ? ORDER BY id", (max_id_dst,)).fetchall()
for r in rows:
dst.execute(f"INSERT OR IGNORE INTO messages ({','.join(cols)}) VALUES ({ph})", r)
dst.commit()
print(f"messages 增量: {len(rows)} 条 (id {max_id_dst}+1 → {src.execute('SELECT MAX(id) FROM messages').fetchone()[0]})")
# 2) system_prompts 增量
hashes_dst = set(x[0] for x in dst.execute("SELECT hash FROM system_prompts"))
added = 0
for h, p in src.execute("SELECT hash, prompt FROM system_prompts"):
if h not in hashes_dst:
try:
dst.execute("INSERT INTO system_prompts (hash, prompt) VALUES (?,?)", (h,p)); added += 1
except Exception as e:
print(f" sp 跳过 {h}: {e}")
dst.commit()
print(f"system_prompts 增量: {added} 条")
# 3) FTS rebuild 对齐
dst.execute("INSERT INTO messages_fts(messages_fts) VALUES('rebuild')")
dst.commit()
try:
dst.execute("INSERT INTO messages_fts_trigram(messages_fts_trigram) VALUES('rebuild')")
dst.commit()
except Exception as e:
print(f"trigram rebuild 跳过: {e}")
dst.rollback()
# 4) 验证
ic = dst.execute("PRAGMA integrity_check").fetchall()
print(f"integrity_check: {ic}")
if ic != [('ok',)]:
print("❌ 恢复库不健康,中止")
sys.exit(1)
for t in ["messages","sessions","system_prompts","delivery_obligations"]:
print(f" {t}: {dst.execute(f'SELECT count(*) FROM {t}').fetchone()[0]}")
print(f" max_msg_id: {dst.execute('SELECT MAX(id) FROM messages').fetchone()[0]} (src: {src.execute('SELECT MAX(id) FROM messages').fetchone()[0]})")
src.close(); dst.close()
PYEOF
PY_EXIT=$?
if [ $PY_EXIT -ne 0 ]; then
log "❌ 恢复库构建失败 (exit=$PY_EXIT) — 保留 live DB 不动,可回滚备份"
exit 1
fi
log "✅ 恢复库 integrity=ok"
# 4. 替换(清旧 WAL/SHM 防串扰)
log "替换 state.db..."
rm -f "$DB-wal" "$DB-shm"
mv "$DB" "$DB.pre-replace-$TS" || { log "❌ mv live 失败"; exit 1; }
mv "$TMP" "$DB" || { log "❌ mv 恢复库失败,回滚"; mv "$DB.pre-replace-$TS" "$DB"; exit 1; }
log " 已替换live 保留在 $DB.pre-replace-$TS"
# 5. 启动 gateway
log "启动 hermes-gateway..."
systemctl --user start hermes-gateway
sleep 10
STATUS=$(systemctl --user is-active hermes-gateway 2>/dev/null)
NEW_PID=$(systemctl --user show hermes-gateway -p MainPID --value 2>/dev/null)
log " gateway: $STATUS PID=$NEW_PID"
# 6. 最终验证gateway 已起,只读查)
IC=$(sqlite3 "$DB" "PRAGMA integrity_check;" 2>&1 | head -1)
M=$(sqlite3 "$DB" "SELECT count(*) FROM messages;" 2>&1)
S=$(sqlite3 "$DB" "SELECT count(*) FROM sessions;" 2>&1)
log " 最终 integrity: $IC | messages: $M | sessions: $S"
# 7. 进度文件(防失忆)
cat > "$PROG" <<EOF
# state.db 修复进度(更新 $TS
- 方案: 19:26 healthy 备份 + messages/system_prompts 增量补回(零丢失)
- 基底: $BASE
- live 备份: $DB.pre-restore-$TS
- live 替换前保留: $DB.pre-replace-$TS
- 结果: integrity=$IC messages=$M sessions=$S
- gateway: $STATUS PID=$NEW_PID
- 完成时间: $(date '+%F %T')
- 下一步: journalctl --user -u hermes-gateway --since '5 min ago' | grep -i malformed (应无报错)
EOF
log "=== 修复完成 ==="
echo "--- 完整日志: $LOG ---"