Agent 2.0 exdev 서버 배포 스택

- server-dev start/stop/deploy 및 Gitea push 자동 배포
- local-dev 로컬 개발 환경

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Macbook
2026-07-21 22:57:30 +09:00
commit 4b86b2a660
344 changed files with 45787 additions and 0 deletions
+428
View File
@@ -0,0 +1,428 @@
#!/bin/bash
set -e
echo "================================================"
echo "RAG 시스템 경량 오프라인 패키지 생성"
echo "================================================"
# 현재 디렉토리 확인
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
PROJECT_DIR="$SCRIPT_DIR"
echo "프로젝트 디렉토리: $PROJECT_DIR"
echo "⚡ 경량 버전 (외부 API 전용 + 로컬 MongoDB): ~500MB"
# 운영 배포 기본값 (Qdrant + 어드민 포함)
PACKAGE_NAME="${PACKAGE_NAME:-rag-offline-package-slim}"
PACKAGE_TAR="${PACKAGE_TAR:-$PACKAGE_NAME.tar.gz}"
API_HOST_PORT="${API_HOST_PORT:-28012}"
ADMIN_PORT="${ADMIN_PORT:-28013}"
COMPOSE_PROJECT_NAME="${COMPOSE_PROJECT_NAME:-rag-project}"
RAG_API_IMAGE="${RAG_API_IMAGE:-rag-api-slim:latest}"
RAG_BATCH_IMAGE="${RAG_BATCH_IMAGE:-rag-batch-slim:latest}"
QDRANT_IMAGE="${QDRANT_IMAGE:-qdrant/qdrant:v1.18.2}"
MONGO_COLLECTION="${MONGO_COLLECTION:-rag_conversations}"
QDRANT_COLLECTION="${QDRANT_COLLECTION:-qa_vectors}"
# 패키지 디렉토리 생성
PACKAGE_DIR="$HOME/$PACKAGE_NAME"
echo "[1/6] 패키지 디렉토리 생성: $PACKAGE_DIR"
rm -rf "$PACKAGE_DIR"
mkdir -p "$PACKAGE_DIR"
echo "테스트 API 포트: $API_HOST_PORT"
echo "Compose 프로젝트명: $COMPOSE_PROJECT_NAME"
echo "API 이미지: $RAG_API_IMAGE"
echo "Batch 이미지: $RAG_BATCH_IMAGE"
echo "Mongo 컬렉션: $MONGO_COLLECTION"
echo "Qdrant 컬렉션: $QDRANT_COLLECTION"
# Docker 이미지 빌드 (경량 버전)
echo "[2/6] Docker 이미지 빌드 중... (2-3분 소요)"
cd "$PROJECT_DIR"
# 경량 docker-compose 사용
# ⚠️ 중요: x86_64(amd64) 아키텍처로 빌드 (CentOS 7 호환)
echo " ⚠️ 플랫폼: linux/amd64 (Intel/AMD x86_64용)"
echo ""
# Docker Buildx로 멀티플랫폼 빌드 설정
export DOCKER_BUILDKIT=1
export COMPOSE_DOCKER_CLI_BUILD=1
# 1단계: 배치 이미지 빌드
echo " [2-1] 배치 이미지 빌드 중 (linux/amd64)..."
docker buildx build --platform linux/amd64 -t "$RAG_BATCH_IMAGE" -f docker/batch-slim.Dockerfile . --load
if [ $? -ne 0 ]; then
echo " ❌ 배치 이미지 빌드 실패"
exit 1
fi
echo " ✅ $RAG_BATCH_IMAGE 빌드 완료"
# 2단계: API 이미지 빌드
echo " [2-2] API 이미지 빌드 중 (linux/amd64)..."
docker buildx build --platform linux/amd64 -t "$RAG_API_IMAGE" -f docker/service-slim.Dockerfile . --load
if [ $? -ne 0 ]; then
echo " ❌ API 이미지 빌드 실패"
exit 1
fi
echo " ✅ $RAG_API_IMAGE 빌드 완료"
# 이미지 확인
echo "[3/6] 빌드된 이미지 확인 중..."
if docker images | grep -q "${RAG_API_IMAGE%%:*}"; then
echo " ✅ $RAG_API_IMAGE"
else
echo " ❌ rag-api-slim 이미지 없음"
exit 1
fi
if docker images | grep -q "${RAG_BATCH_IMAGE%%:*}"; then
echo " ✅ $RAG_BATCH_IMAGE"
else
echo " ❌ rag-batch-slim 이미지 없음"
exit 1
fi
# Docker 이미지 저장
echo "[4/6] Docker 이미지를 tar 파일로 저장 중..."
cd "$PACKAGE_DIR"
echo " - rag-api-slim.tar 저장 중..."
docker save -o rag-api-slim.tar "$RAG_API_IMAGE"
SIZE_API=$(ls -lh rag-api-slim.tar | awk '{print $5}')
echo " 완료: $SIZE_API"
echo " - rag-batch-slim.tar 저장 중..."
docker save -o rag-batch-slim.tar "$RAG_BATCH_IMAGE"
SIZE_BATCH=$(ls -lh rag-batch-slim.tar | awk '{print $5}')
echo " 완료: $SIZE_BATCH"
# Qdrant 서버 이미지 포함 (로컬에 없으면 pull)
echo " - $QDRANT_IMAGE 확인/저장 중..."
if ! docker image inspect "$QDRANT_IMAGE" >/dev/null 2>&1; then
echo " 로컬에 없음 → docker pull $QDRANT_IMAGE"
docker pull "$QDRANT_IMAGE"
fi
docker save -o qdrant.tar "$QDRANT_IMAGE"
SIZE_QDRANT=$(ls -lh qdrant.tar | awk '{print $5}')
echo " 완료: $SIZE_QDRANT"
# 프로젝트 파일 복사
echo "[5/6] 프로젝트 파일 복사 중..."
cp -r "$PROJECT_DIR" "$PACKAGE_DIR/rag-project"
# 불필요한 파일 제거 (이미지 tar/Qdrant 저장소/캐시 등은 패키지에서 제외)
cd "$PACKAGE_DIR/rag-project"
rm -rf __pycache__ scripts/__pycache__ .git .gitignore images qdrant_data
rm -f .env *.zip *.tar *.tar.gz
# data 디렉토리 초기화 (빈 디렉토리만 유지)
rm -rf data/*
touch data/.gitkeep
echo " - data 디렉토리 초기화 완료 (배치 작업 시 자동 생성됨)"
# 기존 docker-compose.yml을 slim 버전으로 교체
mv docker-compose-slim.yml docker-compose.yml
# force-reindex.sh는 유지 (재임베딩용)
chmod +x force-reindex.sh 2>/dev/null || true
echo " 복사된 파일: $(du -sh . | awk '{print $1}')"
# 배포 스크립트 생성
echo "[6/6] 배포 스크립트 생성 중..."
cd "$PACKAGE_DIR"
cat > deploy.sh << 'DEPLOY_SCRIPT'
#!/bin/bash
set -e
echo "================================================"
echo "RAG 시스템 경량 오프라인 배포"
echo "================================================"
GREEN='\033[0;32m'
RED='\033[0;31m'
YELLOW='\033[1;33m'
NC='\033[0m'
# 운영 배포 기본값 (Qdrant + 어드민 포함)
API_HOST_PORT="${API_HOST_PORT:-28012}"
ADMIN_PORT="${ADMIN_PORT:-28013}"
COMPOSE_PROJECT_NAME="${COMPOSE_PROJECT_NAME:-rag-project}"
RAG_API_IMAGE="${RAG_API_IMAGE:-rag-api-slim:latest}"
RAG_BATCH_IMAGE="${RAG_BATCH_IMAGE:-rag-batch-slim:latest}"
MONGO_COLLECTION="${MONGO_COLLECTION:-rag_conversations}"
QDRANT_COLLECTION="${QDRANT_COLLECTION:-qa_vectors}"
VECTOR_STORE_VALUE="${VECTOR_STORE:-qdrant}"
QDRANT_HOST_VALUE="${QDRANT_HOST:-127.0.0.1}"
set_env_value() {
local key=$1
local value=$2
if grep -q "^${key}=" .env; then
sed -i "s|^${key}=.*|${key}=${value}|" .env
else
echo "${key}=${value}" >> .env
fi
}
# Docker 이미지 로드 (rag-api, rag-batch, qdrant)
echo -e "${GREEN}[1/5] Docker 이미지 로딩 중...${NC}"
for tarfile in rag-api-slim.tar rag-batch-slim.tar qdrant.tar; do
if [ -f "$tarfile" ]; then
docker load -i "$tarfile"
echo "✅ $tarfile 로드 완료"
else
echo -e "${RED}❌ $tarfile 파일이 없습니다${NC}"
exit 1
fi
done
# 이미지 확인
echo ""
echo "로드된 이미지:"
docker images | grep -E "rag-.*-slim|qdrant"
# 프로젝트 디렉토리 이동
echo -e "${GREEN}[2/5] 프로젝트 디렉토리 설정 중...${NC}"
cd rag-project
# .env 파일 확인
echo -e "${GREEN}[3/5] 환경 설정 확인 중...${NC}"
if [ ! -f ".env" ]; then
echo -e "${YELLOW}⚠️ .env 파일이 없습니다. env.template을 복사합니다.${NC}"
cp env.template .env
set_env_value "API_HOST_PORT" "$API_HOST_PORT"
set_env_value "ADMIN_PORT" "$ADMIN_PORT"
set_env_value "COMPOSE_PROJECT_NAME" "$COMPOSE_PROJECT_NAME"
set_env_value "RAG_API_IMAGE" "$RAG_API_IMAGE"
set_env_value "RAG_BATCH_IMAGE" "$RAG_BATCH_IMAGE"
set_env_value "MONGO_COLLECTION" "$MONGO_COLLECTION"
set_env_value "QDRANT_COLLECTION" "$QDRANT_COLLECTION"
set_env_value "VECTOR_STORE" "$VECTOR_STORE_VALUE"
set_env_value "QDRANT_HOST" "$QDRANT_HOST_VALUE"
echo ""
echo -e "${RED}❗ 중요: .env 파일을 수정해주세요:${NC}"
echo " vi .env"
echo ""
echo "필수 수정 항목:"
echo " - LLM_HOST=<LLM서버IP>"
echo " - TEI_EMBED_HOST=<TEI서버IP>"
echo " - TEI_RERANK_HOST=<TEI서버IP>"
echo ""
read -p "지금 수정하시겠습니까? (y/n) " answer
if [ "$answer" = "y" ]; then
${EDITOR:-vi} .env
else
echo "나중에 수정하세요: cd rag-project && vi .env"
exit 0
fi
else
set_env_value "API_HOST_PORT" "$API_HOST_PORT"
set_env_value "ADMIN_PORT" "$ADMIN_PORT"
set_env_value "COMPOSE_PROJECT_NAME" "$COMPOSE_PROJECT_NAME"
set_env_value "RAG_API_IMAGE" "$RAG_API_IMAGE"
set_env_value "RAG_BATCH_IMAGE" "$RAG_BATCH_IMAGE"
set_env_value "MONGO_COLLECTION" "$MONGO_COLLECTION"
set_env_value "QDRANT_COLLECTION" "$QDRANT_COLLECTION"
set_env_value "VECTOR_STORE" "$VECTOR_STORE_VALUE"
set_env_value "QDRANT_HOST" "$QDRANT_HOST_VALUE"
fi
# 외부 API 연결 테스트
echo -e "${GREEN}[4/5] 외부 API 연결 테스트 중...${NC}"
source .env
export API_HOST_PORT COMPOSE_PROJECT_NAME RAG_API_IMAGE RAG_BATCH_IMAGE MONGO_COLLECTION QDRANT_COLLECTION
test_api() {
local name=$1
local url=$2
echo -n " - $name: "
if curl -s --connect-timeout 5 "$url/health" > /dev/null 2>&1; then
echo -e "${GREEN}✅ OK${NC}"
return 0
else
echo -e "${RED}❌ FAIL${NC}"
return 1
fi
}
ALL_OK=true
test_api "LLM" "$LLM_BASE_URL" || ALL_OK=false
test_api "TEI Embed" "$TEI_EMBED_URL" || ALL_OK=false
test_api "TEI Rerank" "$TEI_RERANK_URL" || ALL_OK=false
if [ "$ALL_OK" = false ]; then
echo -e "${YELLOW}⚠️ 일부 API 연결 실패. 계속하시겠습니까? (y/n)${NC}"
read -p "> " answer
if [ "$answer" != "y" ]; then
exit 1
fi
fi
# 5) Qdrant 기동 → 임베딩/인덱싱 → 서비스 시작
echo -e "${GREEN}[5/5] Qdrant 기동 → 임베딩/인덱싱 실행 중...${NC}"
# 5-1) Qdrant 먼저 기동 (embed가 의존)
docker compose up -d qdrant
echo "Qdrant 준비 대기 중..."
QDRANT_PORT_VALUE="${QDRANT_PORT:-6333}"
for i in $(seq 1 30); do
if curl -s "http://localhost:${QDRANT_PORT_VALUE}/readyz" > /dev/null 2>&1 \
|| curl -s "http://localhost:${QDRANT_PORT_VALUE}/healthz" > /dev/null 2>&1; then
echo "✅ Qdrant 준비 완료"
break
fi
sleep 1
done
# 5-2) 임베딩 → 인덱싱 (Qdrant로 적재; qdrant 모드에서 index는 자동 스킵)
echo "⚠️ LLM 요약 단계 생략 (qa_raw.jsonl 직접 임베딩)"
if docker compose up embed index; then
echo -e "${GREEN}✅ 배치 작업 완료!${NC}"
else
echo -e "${RED}❌ 배치 작업 실패${NC}"
exit 1
fi
# 6) API + 어드민 서비스 시작
echo ""
echo "================================================"
echo "[6/6] API + 어드민 서비스 시작 중..."
echo "================================================"
docker compose up -d api admin
echo "서비스 시작 대기 중... (5초)"
sleep 5
# 헬스체크
API_BASE_URL="http://localhost:${API_HOST_PORT}"
ADMIN_BASE_URL="http://localhost:${ADMIN_PORT}"
if curl -s "${API_BASE_URL}/health" > /dev/null 2>&1; then
echo -e "${GREEN}✅ API 서비스 정상 동작!${NC}"
echo ""
echo "🔗 엔드포인트:"
echo " - 챗봇 헬스체크: ${API_BASE_URL}/health"
echo " - 챗봇 질의응답: ${API_BASE_URL}/ask"
echo " - 큐레이션 어드민: ${ADMIN_BASE_URL}/ (검색/조회/수정/삭제/추가)"
echo " - Qdrant 대시보드: http://localhost:${QDRANT_PORT_VALUE}/dashboard"
echo ""
echo "📝 테스트 명령어:"
echo " curl -X POST ${API_BASE_URL}/ask -H \"Content-Type: application/json\" -d '{\"query\":\"테스트 질문\"}'"
echo " curl ${ADMIN_BASE_URL}/api/stats"
else
echo -e "${YELLOW}⚠️ 헬스체크 실패. 로그 확인 필요:${NC}"
echo " docker compose logs -f api"
echo " docker compose logs -f admin"
echo " docker compose logs -f qdrant"
fi
echo ""
echo "================================================"
echo -e "${GREEN}배포 완료!${NC}"
echo "================================================"
DEPLOY_SCRIPT
chmod +x deploy.sh
# README 생성
cat > README.txt << 'README'
RAG 시스템 경량 오프라인 운영 패키지 (Qdrant + 어드민)
================================
외부 API(LLM/임베딩/리랭커)를 사용하고, 벡터DB는 Qdrant로 구성하는 운영 배포 패키지입니다.
(외부 API 전용 + 로컬 MongoDB + 로컬 Qdrant)
패키지 크기: ~1GB
패키지 내용:
- rag-api-slim.tar api(챗봇) + admin(어드민) 이미지
- rag-batch-slim.tar embed/index 배치 이미지
- qdrant.tar Qdrant 벡터DB 서버 이미지
- deploy.sh 자동 배포 스크립트
- rag-project/ 프로젝트 파일 (scripts, docker-compose.yml 등)
핵심 특징:
- GPU 불필요 (모든 AI 모델은 외부 API 사용)
- 로컬 MongoDB(27017) + 로컬 Qdrant(6333) 사용
- 완전한 오프라인 배포 가능 (3개 이미지 모두 포함)
- 챗봇 API + 큐레이션 어드민 동시 기동
- 기본 챗봇 API 포트: 28012
- 기본 어드민 포트: 28013
- 기본 Compose 프로젝트명: rag-project
- 기본 이미지 태그: rag-api-slim:latest, rag-batch-slim:latest, qdrant/qdrant:v1.18.2
- 기본 Mongo 컬렉션: rag_conversations
- 기본 Qdrant 컬렉션: qa_vectors
시스템 요구사항:
- OS: Linux (Ubuntu 20.04+, CentOS 7+)
- Docker: 20.10+
- Docker Compose: 1.29+
- MongoDB: localhost:27017 (사전 설치 필요)
* Database: chat_history
* User: exlink / Password: !wkcproqkf1
- 디스크: 2GB 이상
- RAM: 4GB 이상
배포 순서:
1. tar -xzf rag-offline-package-slim.tar.gz
2. cd rag-offline-package-slim
3. rag-project/data/ 디렉토리에 qa_raw.jsonl 파일 배치
(Excel을 JSONL로 변환한 원본 QA 데이터)
4. ./deploy.sh
중요:
- data 디렉토리는 빈 상태로 제공됩니다
- qa_raw.jsonl 파일을 배치해야 배치 작업이 실행됩니다
- 나머지 파일(qa_vecs.jsonl, qa.index 등)은 자동 생성됩니다
- 챗봇 API는 http://localhost:28012, 어드민은 http://localhost:28013 에서 열립니다
- Qdrant 대시보드는 http://localhost:6333/dashboard
- 대화 로그는 rag_conversations 컬렉션에 저장됩니다
- 로컬에 MongoDB가 localhost:27017에서 실행 중이어야 합니다
- 기존에 떠 있던 컨테이너는 배포 전 정리하세요: docker compose down --remove-orphans
재임베딩이 필요한 경우:
- ./force-reindex.sh 실행 (기존 임베딩/인덱스 삭제 후 재생성)
생성일: $(date +%Y-%m-%d)
README
# 전체 패키지 압축
cd "$HOME"
echo "압축 중... (잠시만 기다려주세요)"
tar -czf "$PACKAGE_TAR" "$PACKAGE_NAME/"
# 결과 출력
echo ""
echo "================================================"
echo "경량 패키지 생성 완료!"
echo "================================================"
echo "패키지 위치: $HOME/$PACKAGE_TAR"
FINAL_SIZE=$(ls -lh "$HOME/$PACKAGE_TAR" | awk '{print $5}')
echo "패키지 크기: $FINAL_SIZE"
echo ""
echo "포함된 이미지:"
echo " - $RAG_API_IMAGE (api + admin)"
echo " - $RAG_BATCH_IMAGE (임베딩/인덱싱)"
echo " - $QDRANT_IMAGE (벡터DB)"
echo ""
echo "주의: 로컬 MongoDB (localhost:27017) 사전 설치 필요"
echo " Database: chat_history"
echo " User: exlink / Password: !wkcproqkf1"
echo ""
echo "운영 실행 정보:"
echo " - 챗봇 API 포트: $API_HOST_PORT"
echo " - 어드민 포트: $ADMIN_PORT"
echo " - Compose 프로젝트명: $COMPOSE_PROJECT_NAME"
echo " - Mongo 컬렉션: $MONGO_COLLECTION"
echo " - Qdrant 컬렉션: $QDRANT_COLLECTION"
echo ""
echo "다음 단계:"
echo "1. USB로 전송: cp ~/$PACKAGE_TAR /Volumes/USB/"
echo "2. 서버에서 압축 해제: tar -xzf $PACKAGE_TAR"
echo "3. 원본 데이터 배치: $PACKAGE_NAME/rag-project/data/qa_raw.jsonl"
echo " (Excel을 JSONL로 변환한 QA 데이터 파일)"
echo "4. 배포 실행: cd $PACKAGE_NAME && ./deploy.sh"
echo "================================================"