- legacy_plantai_server_package/: phase_a–f + compile_* + bin/plantai_admin.py + tools/(audit/elf_visibility/smoke/build_delivery_manifest) 完整基线 - legacy_docker_packager/: protected 形态参考 - crypto/client/: plantai_core.py(AES-GCM 原语) + install_plantai.sh 参考 - secrets/provision 严格排除(gitignore),入 git 前已脱敏清理 - LEGACY_MAPPING.md 记录源与改造方向 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
188 lines
7.3 KiB
Bash
Executable File
188 lines
7.3 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
# 阶段 F(供应商侧):备份三个核心知识库数据,打包发给客户。
|
||
# 你的一千多篇文章存在三个地方:
|
||
# 1. rag_kb_store/ ← 原文文件
|
||
# 2. Qdrant ← 向量索引(语义搜索用)
|
||
# 3. Neo4j ← 知识图谱(实体关系用)
|
||
#
|
||
# 客户侧恢复请使用 client/restore_data.sh。
|
||
set -euo pipefail
|
||
umask 077
|
||
|
||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||
[ -f "$SCRIPT_DIR/server.env" ] && . "$SCRIPT_DIR/server.env"
|
||
TEST_ROOT="${TEST_ROOT:-$(cd "$SCRIPT_DIR/../.." && pwd)}"
|
||
SERVER_ROOT="${SERVER_ROOT:-$TEST_ROOT/server}"
|
||
VERSION="${VERSION:-1.0.0}"
|
||
OUT_DIR="${OUT_DIR:-$SCRIPT_DIR/out/$VERSION}"
|
||
|
||
usage() {
|
||
cat >&2 <<EOF
|
||
用法: $(basename "$0") [选项]
|
||
|
||
备份三个核心知识库数据,发给客户。
|
||
|
||
必要参数:
|
||
--rag-kb-dir PATH rag_kb_store 所在目录(默认: \$SERVER_ROOT/PlantAI_compiled)
|
||
→ 你的一千多篇原文文件
|
||
|
||
Qdrant 参数:
|
||
--qdrant-url URL Qdrant HTTP 地址(默认: http://127.0.0.1:6333)
|
||
|
||
Neo4j 参数:
|
||
--neo4j-container NAME Neo4j 容器名(默认: neo4j-gene-family)
|
||
--neo4j-version VER Neo4j 镜像版本(默认: 5.15.0-community)
|
||
|
||
跳过选项:
|
||
--skip-rag-kb 跳过文件知识库
|
||
--skip-qdrant 跳过 Qdrant
|
||
--skip-neo4j 跳过 Neo4j
|
||
|
||
其他:
|
||
-h, --help 显示此帮助
|
||
EOF
|
||
exit 1
|
||
}
|
||
|
||
RAG_KB_DIR="${RAG_KB_DIR:-$SERVER_ROOT/PlantAI_compiled}"
|
||
QDRANT_URL="${QDRANT_URL:-http://127.0.0.1:6333}"
|
||
NEO4J_CONTAINER="${NEO4J_CONTAINER:-neo4j-gene-family}"
|
||
NEO4J_VERSION="${NEO4J_VERSION:-5.15.0-community}"
|
||
|
||
SKIP_RAG_KB=false
|
||
SKIP_QDRANT=false
|
||
SKIP_NEO4J=false
|
||
|
||
while [ $# -gt 0 ]; do
|
||
case "$1" in
|
||
--rag-kb-dir) RAG_KB_DIR="$2"; shift 2 ;;
|
||
--qdrant-url) QDRANT_URL="$2"; shift 2 ;;
|
||
--neo4j-container) NEO4J_CONTAINER="$2"; shift 2 ;;
|
||
--neo4j-version) NEO4J_VERSION="$2"; shift 2 ;;
|
||
--skip-rag-kb) SKIP_RAG_KB=true; shift ;;
|
||
--skip-qdrant) SKIP_QDRANT=true; shift ;;
|
||
--skip-neo4j) SKIP_NEO4J=true; shift ;;
|
||
-h|--help) usage ;;
|
||
*) usage ;;
|
||
esac
|
||
done
|
||
|
||
mkdir -p "$OUT_DIR"
|
||
have_cmd() { command -v "$1" >/dev/null 2>&1; }
|
||
|
||
echo "=============================================="
|
||
echo " 供应商侧 - 知识库迁移打包"
|
||
echo " 版本: $VERSION"
|
||
echo " 输出目录: $OUT_DIR"
|
||
echo ""
|
||
echo " 备份内容(你的一千篇文章的三种形态):"
|
||
echo " 1. rag_kb_store/ ← 原文文件"
|
||
echo " 2. Qdrant 快照 ← 向量索引(语义搜索)"
|
||
echo " 3. Neo4j dump ← 知识图谱(实体关系)"
|
||
echo "=============================================="
|
||
|
||
# ============================================================
|
||
# 1. rag_kb_store — 原始文章文件
|
||
# ============================================================
|
||
if [ "$SKIP_RAG_KB" = false ]; then
|
||
if [ -d "$RAG_KB_DIR/rag_kb_store" ]; then
|
||
echo "[1/3] 打包文件知识库 rag_kb_store(原文文件)"
|
||
tar --xattrs --acls -czf "$OUT_DIR/knowledge-$VERSION.tar.gz" \
|
||
-C "$RAG_KB_DIR" rag_kb_store
|
||
echo " -> 大小: $(du -h "$OUT_DIR/knowledge-$VERSION.tar.gz" | cut -f1)"
|
||
else
|
||
echo "[1/3] 错误: $RAG_KB_DIR/rag_kb_store 不存在,请指定 --rag-kb-dir"
|
||
exit 1
|
||
fi
|
||
fi
|
||
|
||
# ============================================================
|
||
# 2. Qdrant — 向量索引(文章段落的数学指纹)
|
||
# ============================================================
|
||
if [ "$SKIP_QDRANT" = false ]; then
|
||
echo "[2/3] Qdrant 向量库快照"
|
||
if have_cmd curl; then
|
||
collections="$(curl -sf "$QDRANT_URL/collections" | python3 -c "
|
||
import json,sys
|
||
data=json.load(sys.stdin)
|
||
cols=data.get('result',data.get('collections',[]))
|
||
for c in cols:
|
||
print(c.get('name',c))
|
||
" 2>/dev/null)" || collections=""
|
||
if [ -n "$collections" ]; then
|
||
mkdir -p "$OUT_DIR/qdrant-snapshots/$VERSION"
|
||
while IFS= read -r col; do
|
||
[ -z "$col" ] && continue
|
||
echo " -> 创建集合 '$col' 快照"
|
||
curl -sf -X POST "$QDRANT_URL/collections/$col/snapshots" >/dev/null && sleep 2
|
||
snaps="$(curl -sf "$QDRANT_URL/collections/$col/snapshots" | python3 -c "
|
||
import json,sys
|
||
data=json.load(sys.stdin)
|
||
for s in data.get('result',[]):
|
||
print(s.get('name',''))
|
||
" 2>/dev/null)" || snaps=""
|
||
for snap in $snaps; do
|
||
snap_path="$("${PLANTAI_PYTHON:-python3}" -c "
|
||
import json,sys
|
||
data=json.load(sys.stdin)
|
||
for s in data.get('result',[]):
|
||
if s.get('name') == '$snap':
|
||
print(s.get('path',''))
|
||
" 2>/dev/null <<< "$(curl -sf "$QDRANT_URL/collections/$col/snapshots")")" || true
|
||
if [ -n "$snap_path" ] && [ -f "$snap_path" ]; then
|
||
cp "$snap_path" "$OUT_DIR/qdrant-snapshots/$VERSION/${col}__${snap}"
|
||
echo " 已保存: ${col}__${snap}"
|
||
fi
|
||
done
|
||
done <<< "$collections"
|
||
echo " -> 快照总大小: $(du -sh "$OUT_DIR/qdrant-snapshots/$VERSION" | cut -f1)"
|
||
else
|
||
echo " -> Qdrant 无响应或无集合,跳过"
|
||
fi
|
||
fi
|
||
fi
|
||
|
||
# ============================================================
|
||
# 3. Neo4j — 知识图谱(文章之间的实体关系网络)
|
||
# ============================================================
|
||
if [ "$SKIP_NEO4J" = false ]; then
|
||
echo "[3/3] Neo4j 图数据库 dump"
|
||
if docker ps --format '{{.Names}}' 2>/dev/null | grep -q "$NEO4J_CONTAINER"; then
|
||
echo " -> 停止容器 $NEO4J_CONTAINER"
|
||
docker stop "$NEO4J_CONTAINER"
|
||
NEO4J_DATA_DIR="$(docker inspect "$NEO4J_CONTAINER" --format '{{range .Mounts}}{{if eq .Destination "/data"}}{{.Source}}{{end}}{{end}}' 2>/dev/null)" || true
|
||
if [ -n "$NEO4J_DATA_DIR" ] && [ -d "$NEO4J_DATA_DIR" ]; then
|
||
mkdir -p "$OUT_DIR/neo4j-backup"
|
||
echo " -> 执行 neo4j-admin database dump"
|
||
docker run --rm \
|
||
-v "${NEO4J_DATA_DIR}:/data" \
|
||
-v "$OUT_DIR/neo4j-backup:/backups" \
|
||
"neo4j:${NEO4J_VERSION}" \
|
||
neo4j-admin database dump neo4j --to-path=/backups --overwrite-destination=true
|
||
echo " -> dump 文件: $(ls -lh "$OUT_DIR/neo4j-backup/")"
|
||
fi
|
||
echo " -> 重启容器 $NEO4J_CONTAINER"
|
||
docker start "$NEO4J_CONTAINER"
|
||
else
|
||
echo " -> 未运行 Neo4j 容器,跳过"
|
||
fi
|
||
fi
|
||
|
||
echo ""
|
||
echo "=============================================="
|
||
echo " 打包完成!发给客户的产物:"
|
||
echo " ┌────────────────────────────────────────────┐"
|
||
echo " │ 1. knowledge-$VERSION.tar.gz ← 原文文件 │"
|
||
ls -lh "$OUT_DIR/knowledge-$VERSION.tar.gz" 2>/dev/null || echo " │ (未打包)"
|
||
echo " │ 2. qdrant-snapshots/ ← 向量索引 │"
|
||
ls -lh "$OUT_DIR/qdrant-snapshots/$VERSION/" 2>/dev/null | head -3 || echo " │ (未打包)"
|
||
echo " │ 3. neo4j-backup/ ← 知识图谱 │"
|
||
ls -lh "$OUT_DIR/neo4j-backup/" 2>/dev/null | head -3 || echo " │ (未打包)"
|
||
echo " └────────────────────────────────────────────┘"
|
||
echo ""
|
||
echo " 客户机上执行 client/restore_data.sh 恢复。"
|
||
echo " 另外还需要发给客户的(跟知识库无关):"
|
||
echo " - plantai-rag-code-<VERSION>.tar.gz (代码包)"
|
||
echo " - plantai-provision-<DEVICE>.tar.gz (授权文件)"
|
||
echo "=============================================="
|