mirror of
https://github.com/TencentCloud/TencentDB-Agent-Memory
synced 2026-07-10 20:34:30 +00:00
228 lines
8.8 KiB
Bash
228 lines
8.8 KiB
Bash
|
|
#!/usr/bin/env bash
|
||
|
|
# OpenClaw + memory-tencentdb(原 memory-tdai)诊断数据导出脚本
|
||
|
|
# 注:插件已更名为 memory-tencentdb,但数据目录始终为 memory-tdai(代码硬编码)
|
||
|
|
# 用法: bash export-diagnostic.sh [输出目录]
|
||
|
|
# 默认输出到 ~/Downloads/openclaw-diagnostic-<timestamp>/
|
||
|
|
|
||
|
|
set -euo pipefail
|
||
|
|
|
||
|
|
# ── 参数 ──
|
||
|
|
OUTPUT_BASE="${1:-$HOME/Downloads}"
|
||
|
|
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
|
||
|
|
EXPORT_DIR="${OUTPUT_BASE}/openclaw-diagnostic-${TIMESTAMP}"
|
||
|
|
ARCHIVE_PATH="${EXPORT_DIR}.tar.gz"
|
||
|
|
|
||
|
|
# ── OpenClaw 工作目录探测 ──
|
||
|
|
if [ -n "${OPENCLAW_STATE_DIR:-}" ]; then
|
||
|
|
STATE_DIR="$OPENCLAW_STATE_DIR"
|
||
|
|
elif [ -d "$HOME/.openclaw" ]; then
|
||
|
|
STATE_DIR="$HOME/.openclaw"
|
||
|
|
elif [ -d "$HOME/.clawdbot" ]; then
|
||
|
|
STATE_DIR="$HOME/.clawdbot"
|
||
|
|
else
|
||
|
|
echo "❌ 未找到 OpenClaw 工作目录 (~/.openclaw 或 ~/.clawdbot)"
|
||
|
|
exit 1
|
||
|
|
fi
|
||
|
|
|
||
|
|
echo "📂 OpenClaw 工作目录: $STATE_DIR"
|
||
|
|
echo "📦 导出目录: $EXPORT_DIR"
|
||
|
|
|
||
|
|
mkdir -p "$EXPORT_DIR"
|
||
|
|
|
||
|
|
# ── 1. 收集环境信息 ──
|
||
|
|
echo "🔍 收集环境信息..."
|
||
|
|
{
|
||
|
|
echo "=== 导出时间 ==="
|
||
|
|
date -Iseconds 2>/dev/null || date
|
||
|
|
echo ""
|
||
|
|
echo "=== 系统信息 ==="
|
||
|
|
echo "OS: $(uname -a)"
|
||
|
|
echo "Node: $(node --version 2>/dev/null || echo 'not found')"
|
||
|
|
echo "pnpm: $(pnpm --version 2>/dev/null || echo 'not found')"
|
||
|
|
echo ""
|
||
|
|
echo "=== OpenClaw 版本 ==="
|
||
|
|
openclaw --version 2>/dev/null || pnpm openclaw --version 2>/dev/null || echo "(unknown)"
|
||
|
|
echo ""
|
||
|
|
echo "=== 工作目录 ==="
|
||
|
|
echo "STATE_DIR: $STATE_DIR"
|
||
|
|
echo ""
|
||
|
|
echo "=== 目录结构 ==="
|
||
|
|
ls -la "$STATE_DIR/" 2>/dev/null || echo "(empty)"
|
||
|
|
echo ""
|
||
|
|
echo "=== memory-tdai 目录结构 ==="
|
||
|
|
ls -laR "$STATE_DIR/memory-tdai/" 2>/dev/null || echo "(not found)"
|
||
|
|
echo ""
|
||
|
|
echo "=== 磁盘占用 ==="
|
||
|
|
du -sh "$STATE_DIR/memory-tdai/"* 2>/dev/null || echo "(not found)"
|
||
|
|
} > "$EXPORT_DIR/env-info.txt" 2>&1
|
||
|
|
|
||
|
|
# ── 2. 收集 OpenClaw 日志 ──
|
||
|
|
echo "📋 收集 OpenClaw 日志..."
|
||
|
|
mkdir -p "$EXPORT_DIR/logs"
|
||
|
|
|
||
|
|
# 网关日志 (~/.openclaw/logs/)
|
||
|
|
if [ -d "$STATE_DIR/logs" ]; then
|
||
|
|
cp -r "$STATE_DIR/logs/" "$EXPORT_DIR/logs/gateway-logs/" 2>/dev/null || true
|
||
|
|
fi
|
||
|
|
|
||
|
|
# 滚动日志 (/tmp/openclaw/)
|
||
|
|
TMP_LOG_DIR="/tmp/openclaw"
|
||
|
|
if [ -d "$TMP_LOG_DIR" ]; then
|
||
|
|
mkdir -p "$EXPORT_DIR/logs/rolling-logs"
|
||
|
|
# 只取最近 3 个日志文件
|
||
|
|
ls -t "$TMP_LOG_DIR"/openclaw-*.log 2>/dev/null | head -3 | while read -r f; do
|
||
|
|
# 每个文件只取最后 5000 行,避免过大
|
||
|
|
tail -5000 "$f" > "$EXPORT_DIR/logs/rolling-logs/$(basename "$f")" 2>/dev/null || true
|
||
|
|
done
|
||
|
|
fi
|
||
|
|
|
||
|
|
# ── 3. 收集记忆插件数据 ──
|
||
|
|
# 注:数据目录名为 memory-tdai(历史原因,插件更名为 memory-tencentdb 后未改目录名)
|
||
|
|
echo "🧠 收集记忆插件数据..."
|
||
|
|
MEMORY_DIR="$STATE_DIR/memory-tdai"
|
||
|
|
if [ -d "$MEMORY_DIR" ]; then
|
||
|
|
mkdir -p "$EXPORT_DIR/memory-tdai"
|
||
|
|
|
||
|
|
# L0 对话记录 (JSONL)
|
||
|
|
if [ -d "$MEMORY_DIR/conversations" ]; then
|
||
|
|
cp -r "$MEMORY_DIR/conversations/" "$EXPORT_DIR/memory-tdai/conversations/" 2>/dev/null || true
|
||
|
|
fi
|
||
|
|
|
||
|
|
# L1 结构化记忆 (JSONL)
|
||
|
|
if [ -d "$MEMORY_DIR/records" ]; then
|
||
|
|
cp -r "$MEMORY_DIR/records/" "$EXPORT_DIR/memory-tdai/records/" 2>/dev/null || true
|
||
|
|
fi
|
||
|
|
|
||
|
|
# L2 场景文件 (Markdown)
|
||
|
|
if [ -d "$MEMORY_DIR/scene_blocks" ]; then
|
||
|
|
cp -r "$MEMORY_DIR/scene_blocks/" "$EXPORT_DIR/memory-tdai/scene_blocks/" 2>/dev/null || true
|
||
|
|
fi
|
||
|
|
|
||
|
|
# L3 用户画像
|
||
|
|
[ -f "$MEMORY_DIR/persona.md" ] && cp "$MEMORY_DIR/persona.md" "$EXPORT_DIR/memory-tdai/" 2>/dev/null || true
|
||
|
|
|
||
|
|
# checkpoint + scene_index
|
||
|
|
if [ -d "$MEMORY_DIR/.metadata" ]; then
|
||
|
|
cp -r "$MEMORY_DIR/.metadata/" "$EXPORT_DIR/memory-tdai/.metadata/" 2>/dev/null || true
|
||
|
|
fi
|
||
|
|
|
||
|
|
# SQLite 数据库(用于检查向量/FTS索引状态)
|
||
|
|
[ -f "$MEMORY_DIR/vectors.db" ] && cp "$MEMORY_DIR/vectors.db" "$EXPORT_DIR/memory-tdai/" 2>/dev/null || true
|
||
|
|
|
||
|
|
# 备份目录(可选,可能较大)
|
||
|
|
if [ -d "$MEMORY_DIR/.backup" ]; then
|
||
|
|
cp -r "$MEMORY_DIR/.backup/" "$EXPORT_DIR/memory-tdai/.backup/" 2>/dev/null || true
|
||
|
|
fi
|
||
|
|
else
|
||
|
|
echo " ⚠️ 未找到 memory-tdai 数据目录(memory-tencentdb 插件的数据也存储在此目录)"
|
||
|
|
fi
|
||
|
|
|
||
|
|
# ── 4. 收集 OpenClaw 配置(脱敏) ──
|
||
|
|
echo "🔧 收集 OpenClaw 配置(已脱敏)..."
|
||
|
|
CONFIG_FILE="$STATE_DIR/openclaw.json"
|
||
|
|
if [ -f "$CONFIG_FILE" ]; then
|
||
|
|
# 使用 node 脱敏处理配置
|
||
|
|
node -e "
|
||
|
|
const fs = require('fs');
|
||
|
|
const JSON5 = (() => { try { return require('json5'); } catch { return JSON; } })();
|
||
|
|
const raw = fs.readFileSync('$CONFIG_FILE', 'utf-8');
|
||
|
|
let cfg;
|
||
|
|
try { cfg = JSON5.parse(raw); } catch { cfg = JSON.parse(raw); }
|
||
|
|
|
||
|
|
// 递归脱敏函数
|
||
|
|
function redact(obj, path) {
|
||
|
|
if (!obj || typeof obj !== 'object') return obj;
|
||
|
|
if (Array.isArray(obj)) return obj.map((v, i) => redact(v, path + '[' + i + ']'));
|
||
|
|
const result = {};
|
||
|
|
for (const [k, v] of Object.entries(obj)) {
|
||
|
|
const fullPath = path ? path + '.' + k : k;
|
||
|
|
// 脱敏规则:API key、token、password、secret 类字段
|
||
|
|
if (/api_?key|token|password|secret|credential/i.test(k) && typeof v === 'string') {
|
||
|
|
result[k] = v.length > 0 ? '***REDACTED(' + v.length + 'chars)***' : '';
|
||
|
|
}
|
||
|
|
// 脱敏 SecretRef 对象
|
||
|
|
else if (v && typeof v === 'object' && v.source && v.id && v.provider) {
|
||
|
|
result[k] = { source: v.source, provider: v.provider, id: '***REDACTED***' };
|
||
|
|
}
|
||
|
|
// 整体跳过的顶层敏感块
|
||
|
|
else if (['models', 'secrets', 'channels', 'env'].includes(k) && !path) {
|
||
|
|
result[k] = '***REDACTED_SECTION(use openclaw config get ' + k + ' to inspect)***';
|
||
|
|
}
|
||
|
|
// gateway.auth 内的 token/password
|
||
|
|
else if (path === 'gateway.auth' && /token|password/i.test(k)) {
|
||
|
|
result[k] = typeof v === 'string' ? '***REDACTED***' : v;
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
result[k] = redact(v, fullPath);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return result;
|
||
|
|
}
|
||
|
|
|
||
|
|
const redacted = redact(cfg, '');
|
||
|
|
// plugins 已经过递归 redact(),其中 apiKey/token/password/secret 等字段
|
||
|
|
// 会被自动脱敏,同时保留 provider/model/enabled 等排查所需的非敏感配置
|
||
|
|
|
||
|
|
fs.writeFileSync('$EXPORT_DIR/openclaw-config-redacted.json', JSON.stringify(redacted, null, 2));
|
||
|
|
console.log(' ✅ 配置已脱敏导出');
|
||
|
|
" 2>&1 || {
|
||
|
|
echo " ⚠️ Node 脱敏失败,使用 grep 粗略脱敏"
|
||
|
|
# 粗略脱敏:删除包含敏感关键字的行
|
||
|
|
grep -v -iE '(api.?key|token|password|secret|credential).*:.*"[^"]{8,}"' "$CONFIG_FILE" \
|
||
|
|
| sed -E 's/"(models|secrets|channels|env)"\s*:\s*\{[^}]*\}/"__REDACTED_SECTION__"/g' \
|
||
|
|
> "$EXPORT_DIR/openclaw-config-redacted.json" 2>/dev/null || true
|
||
|
|
}
|
||
|
|
else
|
||
|
|
echo " ⚠️ 未找到配置文件"
|
||
|
|
fi
|
||
|
|
|
||
|
|
# ── 5. 收集插件安装信息 ──
|
||
|
|
echo "🔌 收集插件安装信息..."
|
||
|
|
if [ -d "$STATE_DIR/extensions" ]; then
|
||
|
|
{
|
||
|
|
echo "=== 已安装插件 ==="
|
||
|
|
ls -la "$STATE_DIR/extensions/" 2>/dev/null
|
||
|
|
echo ""
|
||
|
|
for ext_dir in "$STATE_DIR/extensions"/*/; do
|
||
|
|
[ -d "$ext_dir" ] || continue
|
||
|
|
pkg="$ext_dir/node_modules/openclaw/package.json"
|
||
|
|
plugin_pkg="$ext_dir/package.json"
|
||
|
|
echo "--- $(basename "$ext_dir") ---"
|
||
|
|
if [ -f "$plugin_pkg" ]; then
|
||
|
|
node -e "const p=require('$plugin_pkg'); console.log('name:', p.name, 'version:', p.version)" 2>/dev/null || true
|
||
|
|
fi
|
||
|
|
done
|
||
|
|
} > "$EXPORT_DIR/plugins-info.txt" 2>&1
|
||
|
|
fi
|
||
|
|
|
||
|
|
# ── 6. 打包 ──
|
||
|
|
echo "📦 打包中..."
|
||
|
|
cd "$(dirname "$EXPORT_DIR")"
|
||
|
|
tar -czf "$ARCHIVE_PATH" "$(basename "$EXPORT_DIR")"
|
||
|
|
|
||
|
|
# 计算大小
|
||
|
|
ARCHIVE_SIZE=$(du -sh "$ARCHIVE_PATH" | cut -f1)
|
||
|
|
|
||
|
|
echo ""
|
||
|
|
echo "═══════════════════════════════════════════════════"
|
||
|
|
echo " ✅ 诊断数据导出完成"
|
||
|
|
echo "═══════════════════════════════════════════════════"
|
||
|
|
echo ""
|
||
|
|
echo " 📦 压缩包: $ARCHIVE_PATH"
|
||
|
|
echo " 📏 大小: $ARCHIVE_SIZE"
|
||
|
|
echo ""
|
||
|
|
echo " 包含内容:"
|
||
|
|
echo " - env-info.txt — 环境信息、目录结构"
|
||
|
|
echo " - logs/ — OpenClaw 网关日志 + 滚动日志"
|
||
|
|
echo " - memory-tdai/ — 记忆插件全量数据 (L0~L3 + SQLite)"
|
||
|
|
echo " - openclaw-config-redacted.json — 脱敏后的配置文件"
|
||
|
|
echo " - plugins-info.txt — 插件安装信息"
|
||
|
|
echo ""
|
||
|
|
echo " ⚠️ 安全提示:"
|
||
|
|
echo " - 配置文件已自动脱敏(API Key、Token、Password 等已移除)"
|
||
|
|
echo " - models/secrets/channels/env 等敏感配置块已整体替换"
|
||
|
|
echo " - 记忆数据中可能包含用户对话内容,请确认后再发送"
|
||
|
|
echo ""
|
||
|
|
echo " 📤 请手动检查后发送给研发团队"
|
||
|
|
echo "═══════════════════════════════════════════════════"
|