4b86b2a660
- server-dev start/stop/deploy 및 Gitea push 자동 배포 - local-dev 로컬 개발 환경 Co-authored-by: Cursor <cursoragent@cursor.com>
101 lines
3.3 KiB
Bash
Executable File
101 lines
3.3 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# exdev Qdrant qa_vectors 스냅샷 → 로컬 Qdrant(:6335) 복원
|
|
#
|
|
# ./local-dev/sync-qdrant-from-exdev.sh
|
|
# ./local-dev/sync-qdrant-from-exdev.sh --force
|
|
set -euo pipefail
|
|
|
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
RUN_DIR="${ROOT_DIR}/local-dev/run"
|
|
SNAP_DIR="${RUN_DIR}/qdrant-snapshots"
|
|
|
|
EXDEV_USER="${EXDEV_USER:-aidev}"
|
|
EXDEV_HOST="${EXDEV_HOST:-chatbot.exdev.co.kr}"
|
|
REMOTE_QDRANT="${REMOTE_QDRANT:-http://127.0.0.1:6333}"
|
|
LOCAL_QDRANT="${LOCAL_QDRANT:-http://127.0.0.1:6335}"
|
|
COLLECTION="${QDRANT_COLLECTION:-qa_vectors}"
|
|
|
|
FORCE=false
|
|
for arg in "$@"; do
|
|
case "$arg" in
|
|
--force) FORCE=true ;;
|
|
-h|--help)
|
|
sed -n '2,6p' "$0"
|
|
exit 0
|
|
;;
|
|
esac
|
|
done
|
|
|
|
log() { echo "[$(date '+%H:%M:%S')] $*"; }
|
|
fail() { log "ERROR: $*"; exit 1; }
|
|
|
|
mkdir -p "${SNAP_DIR}"
|
|
|
|
local_count() {
|
|
curl -sf "${LOCAL_QDRANT}/collections/${COLLECTION}" 2>/dev/null \
|
|
| python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('result',{}).get('points_count',0))" 2>/dev/null \
|
|
|| echo 0
|
|
}
|
|
|
|
remote_count() {
|
|
ssh "${EXDEV_USER}@${EXDEV_HOST}" \
|
|
"curl -sf '${REMOTE_QDRANT}/collections/${COLLECTION}'" 2>/dev/null \
|
|
| python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('result',{}).get('points_count',0))" 2>/dev/null \
|
|
|| echo 0
|
|
}
|
|
|
|
curl -sf "${LOCAL_QDRANT}/readyz" >/dev/null 2>&1 \
|
|
|| curl -sf "${LOCAL_QDRANT}/collections" >/dev/null 2>&1 \
|
|
|| fail "로컬 Qdrant 미기동 — start-all.sh 먼저 실행"
|
|
|
|
lc="$(local_count)"
|
|
rc="$(remote_count)"
|
|
log "로컬 points: ${lc}, exdev points: ${rc}"
|
|
|
|
if [[ "${lc}" -gt 0 ]] && ! $FORCE; then
|
|
log "로컬에 이미 ${lc}건 — 건너뜀 (--force 로 덮어쓰기)"
|
|
exit 0
|
|
fi
|
|
|
|
log "exdev 스냅샷 생성..."
|
|
snap_json="$(ssh "${EXDEV_USER}@${EXDEV_HOST}" \
|
|
"curl -sf -X POST '${REMOTE_QDRANT}/collections/${COLLECTION}/snapshots'")"
|
|
snap_name="$(printf '%s' "${snap_json}" | python3 -c "import sys,json; print(json.load(sys.stdin)['result']['name'])")"
|
|
snap_size="$(printf '%s' "${snap_json}" | python3 -c "import sys,json; print(json.load(sys.stdin)['result']['size'])")"
|
|
log "스냅샷: ${snap_name} ($(numfmt --to=iec "${snap_size}" 2>/dev/null || echo "${snap_size} bytes"))"
|
|
|
|
local_file="${SNAP_DIR}/${snap_name}"
|
|
log "다운로드 → ${local_file}"
|
|
ssh "${EXDEV_USER}@${EXDEV_HOST}" \
|
|
"curl -sf '${REMOTE_QDRANT}/collections/${COLLECTION}/snapshots/${snap_name}'" \
|
|
> "${local_file}"
|
|
|
|
[[ -s "${local_file}" ]] || fail "스냅샷 파일 비어 있음"
|
|
|
|
log "로컬 컬렉션 삭제 후 복원..."
|
|
curl -sf -X DELETE "${LOCAL_QDRANT}/collections/${COLLECTION}" >/dev/null 2>&1 || true
|
|
|
|
restore_json="$(curl -sf -X POST \
|
|
"${LOCAL_QDRANT}/collections/${COLLECTION}/snapshots/upload?priority=snapshot" \
|
|
-H "Content-Type: multipart/form-data" \
|
|
-F "snapshot=@${local_file}")" || fail "스냅샷 업로드 실패"
|
|
|
|
log "복원 응답: ${restore_json}"
|
|
|
|
for i in $(seq 1 30); do
|
|
lc="$(local_count)"
|
|
if [[ "${lc}" -ge "$(( rc * 9 / 10 ))" ]] && [[ "${lc}" -gt 0 ]]; then
|
|
log "✅ Qdrant 동기화 완료 — ${lc} points"
|
|
curl -sf "${ROOT_DIR}/exAiChatBot-chatbot2.0-agent/.env" >/dev/null 2>&1 || true
|
|
exit 0
|
|
fi
|
|
sleep 2
|
|
done
|
|
|
|
lc="$(local_count)"
|
|
if [[ "${lc}" -gt 0 ]]; then
|
|
log "✅ Qdrant 동기화 완료 — ${lc} points (일부만 반영됐을 수 있음)"
|
|
else
|
|
fail "복원 후 points_count=0"
|
|
fi
|