From 0ca78640ee82e34b6875d947b666081f2dd0a0e9 Mon Sep 17 00:00:00 2001 From: Hyungi Ahn Date: Fri, 3 Apr 2026 07:47:09 +0900 Subject: [PATCH] infra: migrate application from Mac mini to GPU server MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Integrate ollama + ai-gateway into root docker-compose.yml (NVIDIA GPU runtime, single compose for all services) - Change NAS mount from SMB (NAS_SMB_PATH) to NFS (NAS_NFS_PATH) Default: /mnt/nas/Document_Server (fstab registered on GPU server) - Update config.yaml AI endpoints: primary → Mac mini MLX via Tailscale (100.76.254.116:8800) fallback/embedding/vision/rerank → ollama (same Docker network) gateway → ai-gateway (same Docker network) - Update credentials.env.example (remove GPU_SERVER_IP, add NFS path) - Mark gpu-server/docker-compose.yml as deprecated - Update CLAUDE.md network diagram and AI model config - Update architecture.md, deploy.md, devlog.md for GPU server as main - Caddyfile: auto_https off, HTTP only (TLS at upstream proxy) - Caddy port: 127.0.0.1:8080:80 (localhost only) Co-Authored-By: Claude Opus 4.6 (1M context) --- CLAUDE.md | 46 ++++----- Caddyfile | 8 +- README.md | 2 +- config.yaml | 12 +-- credentials.env.example | 20 ++-- docker-compose.yml | 36 ++++++- docs/architecture.md | 4 +- docs/deploy.md | 2 +- docs/devlog.md | 181 ++++++++++++++++++++++++++++++++++ docs/gpu-migration-plan.md | 174 ++++++++++++++++++++++++++++++++ gpu-server/docker-compose.yml | 5 +- 11 files changed, 434 insertions(+), 56 deletions(-) create mode 100644 docs/devlog.md create mode 100644 docs/gpu-migration-plan.md diff --git a/CLAUDE.md b/CLAUDE.md index f8d27b6..e97957f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -27,25 +27,26 @@ Mac mini M4 Pro를 애플리케이션 서버, Synology NAS를 파일 저장소, ## 네트워크 환경 ``` -Mac mini M4 Pro (애플리케이션 서버): - - Docker Compose: FastAPI(:8000), PostgreSQL(:5432), kordoc(:3100), Caddy(:80,:443) - - MLX Server: http://localhost:8800/v1/chat/completions (Qwen3.5-35B-A3B) - - 외부 접근: pkm.hyungi.net (Caddy 프록시) +GPU 서버 (RTX 4070 Ti Super, Ubuntu, 메인 서버): + - Docker Compose: FastAPI(:8000), PostgreSQL(:5432), kordoc(:3100), + Caddy(:8080 HTTP only), Ollama(:11434), AI Gateway(:8081), frontend(:3000) + - NFS 마운트: /mnt/nas/Document_Server → NAS /volume4/Document_Server + - 외부 접근: document.hyungi.net (앞단 프록시 → Caddy) + - 로컬 IP: 192.168.1.186 + +Mac mini M4 Pro (AI 서버): + - MLX Server: http://100.76.254.116:8800/v1/chat/completions (Qwen3.5-35B-A3B) + - Tailscale IP: 100.76.254.116 Synology NAS (DS1525+): - 도메인: ds1525.hyungi.net - Tailscale IP: 100.101.79.37 - 포트: 15001 - 파일 원본: /volume4/Document_Server/PKM/ + - NFS export → GPU 서버 - Synology Office: 문서 편집/미리보기 - Synology Calendar: CalDAV 태스크 관리 (OmniFocus 대체) - MailPlus: IMAP(993) + SMTP(465) - -GPU 서버 (RTX 4070 Ti Super): - - AI Gateway: http://gpu-server:8080 (모델 라우팅, 폴백, 비용 제어) - - nomic-embed-text: 벡터 임베딩 - - Qwen2.5-VL-7B: 이미지/도면 OCR - - bge-reranker-v2-m3: RAG 리랭킹 ``` ## 인증 정보 @@ -57,22 +58,22 @@ GPU 서버 (RTX 4070 Ti Super): ## AI 모델 구성 ``` -Primary (Mac mini MLX, 상시, 무료): +Primary (Mac mini MLX, Tailscale 경유, 상시, 무료): mlx-community/Qwen3.5-35B-A3B-4bit — 분류, 태그, 요약 - → http://localhost:8800/v1/chat/completions + → http://100.76.254.116:8800/v1/chat/completions -Fallback (GPU Ollama, MLX 장애 시): +Fallback (GPU Ollama, 같은 Docker 네트워크, MLX 장애 시): qwen3.5:35b-a3b - → http://gpu-server:11434/v1/chat/completions + → http://ollama:11434/v1/chat/completions Premium (Claude API, 종량제, 수동 트리거만): claude-sonnet — 복잡한 분석, 장문 처리 → 일일 한도 $5, require_explicit_trigger: true -Embedding (GPU 서버 전용): - nomic-embed-text → 벡터 임베딩 - Qwen2.5-VL-7B → 이미지/도면 OCR - bge-reranker-v2-m3 → RAG 리랭킹 +Embedding (GPU Ollama, 같은 Docker 네트워크): + nomic-embed-text → 벡터 임베딩 → http://ollama:11434/api/embeddings + Qwen2.5-VL-7B → 이미지/도면 OCR → http://ollama:11434/api/generate + bge-reranker-v2-m3 → RAG 리랭킹 → http://ollama:11434/api/rerank ``` ## 프로젝트 구조 @@ -120,21 +121,16 @@ hyungi_Document_Server/ ## 개발/배포 워크플로우 ``` -MacBook Pro (개발) → Gitea push → 서버에서 pull +MacBook Pro (개발) → Gitea push → GPU 서버에서 pull 개발: cd ~/Documents/code/hyungi_Document_Server/ # 코드 작성 → git commit & push -Mac mini 배포: +GPU 서버 배포 (메인): cd ~/Documents/code/hyungi_Document_Server/ git pull docker compose up -d - -GPU 서버 배포: - cd ~/Documents/code/hyungi_Document_Server/gpu-server/ - git pull - docker compose up -d ``` ## v1 코드 참조 diff --git a/Caddyfile b/Caddyfile index e1f0493..535aadf 100644 --- a/Caddyfile +++ b/Caddyfile @@ -1,4 +1,8 @@ -:80 { +{ + auto_https off +} + +http://document.hyungi.net { encode gzip # API + 문서 → FastAPI @@ -25,7 +29,7 @@ } # Synology Office 프록시 -office.hyungi.net { +http://office.hyungi.net { reverse_proxy https://ds1525.hyungi.net:5001 { header_up Host {upstream_hostport} transport http { diff --git a/README.md b/README.md index e6e82b1..5be274d 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ Self-hosted 개인 지식관리(PKM) 웹 애플리케이션 ## Quick Start ```bash -git clone https://git.hyungi.net/hyungi/hyungi_document_server.git +git clone https://git.hyungi.net/hyungi/hyungi_document_server.git hyungi_Document_Server cd hyungi_Document_Server # 인증 정보 설정 diff --git a/config.yaml b/config.yaml index 206255c..7d32c9f 100644 --- a/config.yaml +++ b/config.yaml @@ -2,17 +2,17 @@ ai: gateway: - endpoint: "http://gpu-server:8080" + endpoint: "http://ai-gateway:8080" models: primary: - endpoint: "http://host.docker.internal:8800/v1/chat/completions" + endpoint: "http://100.76.254.116:8800/v1/chat/completions" model: "mlx-community/Qwen3.5-35B-A3B-4bit" max_tokens: 4096 timeout: 60 fallback: - endpoint: "http://gpu-server:11434/v1/chat/completions" + endpoint: "http://ollama:11434/v1/chat/completions" model: "qwen3.5:35b-a3b" max_tokens: 4096 timeout: 120 @@ -25,15 +25,15 @@ ai: require_explicit_trigger: true embedding: - endpoint: "http://gpu-server:11434/api/embeddings" + endpoint: "http://ollama:11434/api/embeddings" model: "nomic-embed-text" vision: - endpoint: "http://gpu-server:11434/api/generate" + endpoint: "http://ollama:11434/api/generate" model: "Qwen2.5-VL-7B" rerank: - endpoint: "http://gpu-server:11434/api/rerank" + endpoint: "http://ollama:11434/api/rerank" model: "bge-reranker-v2-m3" nas: diff --git a/credentials.env.example b/credentials.env.example index 4f31062..b08d6b6 100644 --- a/credentials.env.example +++ b/credentials.env.example @@ -10,22 +10,18 @@ POSTGRES_DB=pkm POSTGRES_USER=pkm POSTGRES_PASSWORD= -# ─── AI: Mac mini MLX (Qwen3.5, 기본 모델) ─── -MLX_ENDPOINT=http://localhost:8800/v1/chat/completions +# ─── AI: Mac mini MLX (Tailscale 경유, Qwen3.5 기본 모델) ─── +MLX_ENDPOINT=http://100.76.254.116:8800/v1/chat/completions MLX_MODEL=mlx-community/Qwen3.5-35B-A3B-4bit -# ─── AI: GPU 서버 ─── -GPU_SERVER_IP= -GPU_EMBED_PORT=11434 - # ─── AI: Claude API (종량제, 복잡한 분석 전용) ─── CLAUDE_API_KEY= -# ─── AI Gateway (GPU 서버) ─── -AI_GATEWAY_ENDPOINT=http://gpu-server:8080 +# ─── AI Gateway (같은 Docker 네트워크) ─── +AI_GATEWAY_ENDPOINT=http://ai-gateway:8080 -# ─── Synology NAS ─── -NAS_SMB_PATH=/Volumes/Document_Server +# ─── NAS (NFS 마운트) ─── +NAS_NFS_PATH=/mnt/nas/Document_Server NAS_DOMAIN=ds1525.hyungi.net NAS_TAILSCALE_IP=100.101.79.37 NAS_PORT=15001 @@ -51,7 +47,3 @@ TOTP_SECRET= # ─── 국가법령정보센터 (법령 모니터링) ─── LAW_OC= - -# ─── TKSafety API (나중에 활성화) ─── -#TKSAFETY_HOST=tksafety.technicalkorea.net -#TKSAFETY_PORT= diff --git a/docker-compose.yml b/docker-compose.yml index c5fe68d..7197188 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -22,7 +22,7 @@ services: ports: - "3100:3100" volumes: - - ${NAS_SMB_PATH:-/Volumes/Document_Server}:/documents:ro + - ${NAS_NFS_PATH:-/mnt/nas/Document_Server}:/documents:ro healthcheck: test: ["CMD", "node", "-e", "fetch('http://localhost:3100/health').then(r=>{process.exit(r.ok?0:1)}).catch(()=>process.exit(1))"] interval: 10s @@ -30,12 +30,40 @@ services: retries: 3 restart: unless-stopped + ollama: + image: ollama/ollama + volumes: + - ollama_data:/root/.ollama + deploy: + resources: + reservations: + devices: + - driver: nvidia + count: 1 + capabilities: [gpu] + ports: + - "11434:11434" + restart: unless-stopped + + ai-gateway: + build: ./gpu-server/services/ai-gateway + ports: + - "8081:8080" + environment: + - PRIMARY_ENDPOINT=http://100.76.254.116:8800/v1/chat/completions + - FALLBACK_ENDPOINT=http://ollama:11434/v1/chat/completions + - CLAUDE_API_KEY=${CLAUDE_API_KEY:-} + - DAILY_BUDGET_USD=${DAILY_BUDGET_USD:-5.00} + depends_on: + - ollama + restart: unless-stopped + fastapi: build: ./app ports: - "8000:8000" volumes: - - ${NAS_SMB_PATH:-/Volumes/Document_Server}:/documents + - ${NAS_NFS_PATH:-/mnt/nas/Document_Server}:/documents - ./config.yaml:/app/config.yaml:ro - ./scripts:/app/scripts:ro - ./logs:/app/logs @@ -62,8 +90,7 @@ services: caddy: image: caddy:2 ports: - - "8080:80" - - "9443:443" + - "127.0.0.1:8080:80" volumes: - ./Caddyfile:/etc/caddy/Caddyfile - caddy_data:/data @@ -75,3 +102,4 @@ services: volumes: pgdata: caddy_data: + ollama_data: diff --git a/docs/architecture.md b/docs/architecture.md index 4a06690..556f67f 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -542,7 +542,7 @@ POST /to-hwpx ### Caddy 설정 예시 ``` -pkm.hyungi.net { +document.hyungi.net { reverse_proxy localhost:8000 # FastAPI } @@ -931,7 +931,7 @@ pkm-web/ │ └── migrate_from_devonthink.py ← v1 → v2 마이그레이션 스크립트 │ ├── docs/ -│ ├── architecture-v2.md ← 이 문서 +│ ├── architecture.md ← 이 문서 │ └── deploy.md ← 배포 가이드 │ └── tests/ diff --git a/docs/deploy.md b/docs/deploy.md index 8152f3c..809d5ff 100644 --- a/docs/deploy.md +++ b/docs/deploy.md @@ -65,7 +65,7 @@ curl http://localhost:3100/health ### 2-6. 외부 접근 (Caddy) Caddy가 자동으로 HTTPS 인증서를 발급한다. -- `pkm.hyungi.net` → FastAPI (:8000) +- `document.hyungi.net` → FastAPI (:8000) - `office.hyungi.net` → Synology Office (NAS 프록시) DNS 레코드가 Mac mini의 공인 IP를 가리켜야 한다. diff --git a/docs/devlog.md b/docs/devlog.md new file mode 100644 index 0000000..fafce2b --- /dev/null +++ b/docs/devlog.md @@ -0,0 +1,181 @@ +# 개발 로그 + +## 2026-04-02 — v2 전환 설계 완료 + +### 결정 사항 +- DEVONthink 탈피 결정. v1의 구조적 한계(AppleScript 취약성, macOS GUI 의존, 13개 DB 복잡성)를 더 이상 감수하지 않기로 함 +- 자체 웹앱 방향 확정. 기술 스택: FastAPI + PostgreSQL/pgvector + SvelteKit + Docker +- OmniFocus 탈피 → Synology Calendar (CalDAV VTODO)로 대체 +- Synology 서비스 활용 극대화: Office(문서 편집/미리보기), Drive(파일 관리), Calendar(태스크), MailPlus(이메일+알림) +- Document_Server 전체를 Synology Drive가 관리. PKM 하위 폴더로 자동분류 영역 분리 +- 문서 "원본" 정의 확정: immutable(PDF, 수신 HWP 등) / editable(Synology Office 포맷) / note(Markdown) +- .docx/.xlsx는 교환 형식으로 취급. 서버에 영구 보관하지 않음 +- 데이터 3계층: 원본(NAS) → 가공(PostgreSQL) → 파생(pgvector+캐시) +- kordoc 통합 결정 (HWP/HWPX/PDF → Markdown 파싱, Node.js 마이크로서비스) +- AI 전략: Qwen3.5-35B-A3B(MLX) 우선, Claude API는 종량제로 최후 수단. GPU 서버에 AI Gateway 배치 +- Anthropic 약관 확인: 구독 OAuth의 서드파티 사용은 약관상 금지(2026.01~). 자동화에는 API 키만 사용 +- NanoClaw는 선택적 확장(대화형 인터페이스)으로 위치, 핵심 파이프라인 비의존 +- 장기 로드맵: GPU 서버 확장 → 메인 서버 승격, Mac mini → Roon Core 전용, Synology 장기 유지 + +### 산출물 +- `docs/architecture-v2.md` — 17개 섹션 + 부록 2개 (전체 시스템 설계) +- 마이그레이션 계획서 — Step 1~5 (프로젝트 리네임+정리) + Phase 0~5 (v2 개발) +- 프로젝트 리네임: DEVONThink_my server → hyungi_Document_Server + +### 배경 논의 (Cowork 세션) +- v1에서 16개 커밋 중 절반 이상이 AppleScript 버그 수정이었던 점이 전환의 직접적 계기 +- Synology Office iframe 임베드로 DEVONthink 미리보기 대체 가능성 논의 +- HWP 대응으로 kordoc(광진구청 류승인 주무관 제작, MIT 라이선스) 조사 및 채택 +- 편집 가능 문서의 "원본이 뭐냐" 논의 → Synology Office 포맷이 원본, .docx/.xlsx는 교환용 +- 가공 데이터 보관 전략 논의 → 파일로 저장하지 않고 PostgreSQL에만 저장, 버전 추적으로 선택적 재가공 + +## 2026-04-02 — 프로젝트 리네임 + v2 전환 실행 + +### Step 1: 사전 정리 ✅ +- architecture-v2.md 커밋 (`852b7da`) +- v1-archive 브랜치 + v1-final 태그 생성 (v1 상태 완벽 보존) + +### Step 2: v1 파일 정리 ✅ +- v1 전용 파일 git rm 완료 (`e48b6a2`) +- 삭제: applescript/, launchd/, v1 scripts, v1 docs, tests/test_classify.py, requirements.txt +- 유지: scripts/prompts/classify_document.txt, credentials.env.example (v2 필드로 갱신) + +### Step 3: Gitea 리포 리네임 + 로컬 폴더 리네임 ✅ +- Gitea: devonthink_home → hyungi_document_server +- 로컬 폴더: DEVONThink_my server → hyungi_Document_Server +- git remote set-url + git ls-remote 검증 + push 완료 + +### Step 4: 문서 전면 재작성 ✅ +- CLAUDE.md — v2 기준으로 전면 재작성 +- README.md — 프로젝트명, 기술 스택, 디렉토리 구조 갱신 +- docs/deploy.md — Docker Compose 기반 배포 가이드로 교체 +- docs/claude-code-commands.md → docs/development-stages.md 변환 +- docs/architecture-v2.md → docs/architecture.md 승격 + +### Step 5: v2 프로젝트 스캐폴딩 ✅ +- 전체 디렉토리 구조 생성 (app/, services/kordoc/, gpu-server/, frontend/, migrations/, tests/) +- 동작하는 최소 코드 수준: FastAPI main.py, PostgreSQL 스키마, kordoc server.js, config.yaml 등 +- docker-compose.yml, Caddyfile, credentials.env.example 생성 +- tests/__init__.py + conftest.py 포함 + +### Step 1~5 전체 완료. + +--- + +## 2026-04-02 — Phase 0: 기반 구축 시작 + +### users 테이블 + ORM 모델 추가 ✅ +- `migrations/001_initial_schema.sql`에 users 테이블 포함 (username, password_hash, totp_secret, is_active) +- `app/models/user.py` — SQLAlchemy 2.0 Mapped 스타일 ORM 모델 +- architecture.md 섹션 6 스키마와 일치 + +### Auth API 엔드포인트 구현 ✅ +- `app/api/auth.py` — 4개 엔드포인트: POST /login (JWT발급+TOTP), POST /refresh, GET /me, POST /change-password +- `app/core/auth.py` — bcrypt 비밀번호 해싱, JWT 발급/검증, TOTP 검증, get_current_user 의존성 +- Pydantic 스키마: LoginRequest, TokenResponse, RefreshRequest, ChangePasswordRequest, UserResponse + +### main.py 라우터 등록 + health 강화 ✅ +- auth 라우터 등록: `/api/auth` prefix +- health 엔드포인트에 DB 연결 상태 포함 (connected/disconnected) +- lifespan 핸들러로 DB 초기화/정리 +### Docker 설정 수정 ✅ +### 초기 admin 유저 시드 스크립트 ✅ + +### 셋업 위자드 구현 ✅ (`a601991`) +- `app/api/setup.py` — 6개 엔드포인트: GET /status, POST /admin, POST /totp/init, POST /totp/verify, POST /verify-nas, GET / (HTML) +- `app/templates/setup.html` — Jinja2 단일 HTML, Vanilla JS + qrcode.js CDN, 3단계 위자드 +- `app/main.py` — setup 라우터 등록 + 셋업 미들웨어 (유저 0명 시 /setup 리다이렉트, /health /docs 등 바이패스) +- Rate Limiting: IP당 5분 내 5회 실패 시 차단 +- TOTP 흐름: init에서 secret 반환(DB 미저장) → verify에서 코드 검증 후 DB 저장 +- scripts/seed_admin.py CLI 백업 수단 유지 +- requirements.txt에 jinja2 추가 + +### Phase 0 완료 기준 달성 상태 +- ✅ docker compose up → FastAPI 구동 +- ✅ DB 스키마 (users, documents, tasks, processing_queue) +- ✅ JWT + TOTP 인증 (로그인, 토큰 갱신, 비밀번호 변경) +- ✅ 셋업 위자드 (관리자 생성 + TOTP + NAS 확인) +- ✅ /health — DB 연결 상태 포함 +- ✅ /docs — OpenAPI 문서 +- ⬜ NAS SMB 마운트 실제 검증 (Mac mini 배포 시) +- ⬜ config.yaml 로딩 검증 (Mac mini 배포 시) + +--- + +## 2026-04-02 — Phase 1: 데이터 마이그레이션 파이프라인 구현 완료 + +### Step 1: kordoc /parse 실제 구현 ✅ +- `services/kordoc/server.js` — stub → 실제 파싱 구현 (kordoc ^1.7.0 + pdfjs-dist ^4.0.0) +- HWP/HWPX/PDF → Markdown 변환, .md/.txt 직접 읽기, 이미지는 requires_ocr 플래그 반환 +- 타임아웃 30초, 파일 미존재 404, 파싱 실패 422 + +### Step 2: 큐 소비자 인프라 ✅ +- `app/workers/queue_consumer.py` — APScheduler AsyncIOScheduler 1분 간격 실행 +- 배치 처리: extract=5, classify=3, embed=1 +- stage 체이닝: extract → classify → embed 자동 enqueue +- stale 항목 자동 복구 (processing 상태 10분 초과) +- `app/main.py` — lifespan에 APScheduler 연결, yield 후 shutdown 보장 + +### Step 3: 텍스트 추출 워커 ✅ +- `app/workers/extract_worker.py` — 포맷별 분기 처리 +- KORDOC_FORMATS (hwp, hwpx, pdf) → kordoc HTTP POST +- TEXT_FORMATS (md, txt, csv, json, xml, html) → 직접 파일 읽기 +- IMAGE_FORMATS → Phase 2 OCR로 연기 + +### Step 4: AI 분류 워커 ✅ +- `app/workers/classify_worker.py` — extracted_text 8000자 → AIClient.classify() 호출 +- `app/ai/client.py` — strip_thinking(), parse_json_response() 추가 (v1 pkm_utils.py에서 포팅) +- Qwen3.5의 태그 제거 + 비정형 JSON 파싱 로직 + +### Step 5: 벡터 임베딩 워커 ✅ +- `app/workers/embed_worker.py` — nomic-embed-text-v1.5 (GPU 서버), 6000자 제한 +- GPU 서버 불가 시 graceful fail → 재시도 + +### Step 6: DEVONthink 마이그레이션 스크립트 ✅ +- `scripts/migrate_from_devonthink.py` — --dry-run, --source-dir, --target-dir, --database-url 지원 +- DEVONthink 내보내기 → NAS PKM 구조 복사 + documents/processing_queue DB 등록 + +### Phase 1 완료 기준 달성 상태 +- ✅ kordoc 파싱 (HWP/HWPX/PDF → Markdown) +- ✅ 큐 소비자 + APScheduler 연동 +- ✅ extract → classify → embed 워커 3개 +- ✅ AI 클라이언트 think 태그 / JSON 파싱 보강 +- ✅ 마이그레이션 스크립트 +- ⬜ Step 7: 통합 테스트 + 배치 실행 (Mac mini 배포 후) + +--- + +## 2026-04-02 — Phase 2: 핵심 기능 구현 완료 (`4b69533`) + +### 문서 CRUD API ✅ +- `app/api/documents.py` — 5개 엔드포인트 +- POST /api/documents/ — 파일 업로드 (Inbox 저장 + extract 큐 등록) +- GET /api/documents/ — 목록 조회 (페이징 + domain/source/format 필터) +- GET /api/documents/{id} — 단건 조회 +- PATCH /api/documents/{id} — 메타데이터 수동 수정 +- DELETE /api/documents/{id} — DB 삭제 (기본), ?delete_file=true로 파일도 삭제 + +### 하이브리드 검색 API ✅ +- `app/api/search.py` — GET /api/search/?q={query}&mode={mode} +- 4가지 모드: fts, trgm, vector, hybrid (기본) +- hybrid 가중치: FTS 0.4 + Trigram 0.2 + Vector 0.4 +- 벡터 불가 시 FTS 0.6 + Trigram 0.4 폴백 +- 결과에 snippet(200자) 포함 + +### 파일 워처 ✅ +- `app/workers/file_watcher.py` — Inbox 디렉토리 5분 간격 스캔 +- 신규 파일: Document 생성 + extract 큐 등록 +- 변경 파일: 해시 비교 → 재추출 큐 등록 +- .DS_Store, .tmp, .part 등 무시 파일 처리 + +### 벡터 인덱스 마이그레이션 ✅ +- `migrations/002_vector_index.sql` — IVFFlat 인덱스 (cosine, lists=50) +- 문서 수 증가 시 lists 값 조정 필요 + +### Phase 2 완료 기준 달성 상태 +- ✅ 문서 CRUD API (업로드, 목록, 조회, 수정, 삭제) +- ✅ 하이브리드 검색 (FTS + Trigram + Vector) +- ✅ Inbox 파일 워처 (신규/변경 자동 감지 → 파이프라인 등록) +- ✅ 처리 파이프라인 전체 동작 (upload/watch → extract → classify → embed → search) +- ⬜ 문서 뷰어 UI (Phase 4로 이관) +- ⬜ SvelteKit 프론트엔드 (Phase 4로 이관) diff --git a/docs/gpu-migration-plan.md b/docs/gpu-migration-plan.md new file mode 100644 index 0000000..76f6c00 --- /dev/null +++ b/docs/gpu-migration-plan.md @@ -0,0 +1,174 @@ +# GPU 서버 이전 + NFS 전환 — Claude Code 작업 지시서 + +## 배경 + +Phase 0~4 완료. 현재 Mac mini에서 Docker 전체 구동 중. +GPU 서버(Ubuntu, RTX 4070 Ti Super)로 애플리케이션 이전. +NAS NFS 마운트로 Synology Drive 데드락 해결. + +## 완료된 수동 작업 + +- ✅ Step 1: NAS NFS 서버 설정 (DSM에서 NFS 활성화, NFSv4.1) +- ✅ Step 2: GPU 서버 NFS 마운트 (`/mnt/nas/Document_Server`, fstab 등록 완료) +- ✅ Step 6: Mac mini MLX 서버 외부 접근 확인 (100.76.254.116:8800 응답 확인) + +## 확정된 정보 + +- Mac mini Tailscale IP: `100.76.254.116` +- NAS 로컬 IP: `192.168.1.227` +- GPU 서버 로컬 IP: `192.168.1.186` +- NFS 마운트 경로: `/mnt/nas/Document_Server` +- MLX 모델: `mlx-community/Qwen3.5-35B-A3B-4bit` (Mac mini에서 계속 서빙) + +## 목표 구조 + +``` +GPU 서버 (Ubuntu, 메인 서버): + Docker Compose 단일 파일: + - postgres, fastapi, kordoc-service, frontend, caddy + - ollama (NVIDIA GPU), ai-gateway + NFS → NAS /volume4/Document_Server (/mnt/nas/Document_Server) + +Mac mini M4 Pro (AI 서버만): + MLX Server: http://100.76.254.116:8800 (Qwen3.5-35B-A3B) + +NAS DS1525+ (파일 저장소): + NFS export → GPU 서버 + Synology Office/Calendar/MailPlus 유지 +``` + +--- + +## Claude Code 작업 목록 + +### 작업 1: docker-compose.yml 통합 + +현재 루트 `docker-compose.yml` (Mac mini용)에 `gpu-server/docker-compose.yml`의 서비스를 통합. + +변경 사항: +- `version: '3.8'` 제거 (Docker Compose V2 기준) +- NAS 볼륨 변수: `NAS_SMB_PATH` → `NAS_NFS_PATH`, 기본값 `/mnt/nas/Document_Server` +- Ollama 서비스 추가 (NVIDIA GPU runtime, ollama_data 볼륨) +- AI Gateway 서비스 추가 (Ollama depends_on) +- AI Gateway 환경변수: PRIMARY_ENDPOINT=http://100.76.254.116:8800/v1/chat/completions +- Caddy 포트: `127.0.0.1:8080:80` 유지 (HTTPS는 앞단 프록시(UCG-Fiber)에서 처리, Caddy는 HTTP only) +- ollama_data 볼륨 추가 + +참고 — 현재 파일: +- 루트 docker-compose.yml: postgres, kordoc-service, fastapi, frontend, caddy +- gpu-server/docker-compose.yml: ollama, ai-gateway + +### 작업 2: config.yaml AI 엔드포인트 변경 + +현재 → 변경: + +```yaml +ai: + gateway: + endpoint: "http://ai-gateway:8080" # gpu-server → ai-gateway (같은 Docker 네트워크) + + models: + primary: + endpoint: "http://100.76.254.116:8800/v1/chat/completions" # host.docker.internal → Mac mini Tailscale IP + # 나머지 동일 + + fallback: + endpoint: "http://ollama:11434/v1/chat/completions" # gpu-server → ollama (같은 Docker 네트워크) + # 나머지 동일 + + embedding: + endpoint: "http://ollama:11434/api/embeddings" # gpu-server → ollama + + vision: + endpoint: "http://ollama:11434/api/generate" # gpu-server → ollama + + rerank: + endpoint: "http://ollama:11434/api/rerank" # gpu-server → ollama +``` + +핵심: `gpu-server` 호스트명이 전부 `ollama` 또는 `ai-gateway`로 변경 (같은 Docker 네트워크). +primary만 Mac mini Tailscale IP `100.76.254.116`으로 외부 호출. + +### 작업 3: credentials.env.example 갱신 + +변경 사항: +- `NAS_SMB_PATH` → `NAS_NFS_PATH=/mnt/nas/Document_Server` +- `MLX_ENDPOINT` → `http://100.76.254.116:8800/v1/chat/completions` +- `GPU_SERVER_IP` 항목 제거 (로컬이 됨) +- `AI_GATEWAY_ENDPOINT` → `http://ai-gateway:8080` (같은 Docker 네트워크) +- 주석 업데이트: "Mac mini MLX" → "Mac mini MLX (Tailscale 경유)" + +### 작업 4: Caddyfile 확인 + +변경 불필요. 현재 상태 유지: +- `auto_https off` + `http://document.hyungi.net` (HTTPS는 앞단 프록시 UCG-Fiber에서 처리) +- Caddy 포트: `127.0.0.1:8080:80` (localhost 바인딩, 443 불필요) + +### 작업 5: 문서 업데이트 + +#### CLAUDE.md — 네트워크 환경 섹션 갱신 + +현재: +``` +Mac mini M4 Pro (애플리케이션 서버): + - Docker Compose: FastAPI(:8000), PostgreSQL(:5432), kordoc(:3100), Caddy(:80,:443) + - MLX Server: http://localhost:8800/v1/chat/completions (Qwen3.5-35B-A3B) + - 외부 접근: document.hyungi.net (Caddy 프록시) +``` + +변경: +``` +GPU 서버 (RTX 4070 Ti Super, Ubuntu, 메인 서버): + - Docker Compose: FastAPI(:8000), PostgreSQL(:5432), kordoc(:3100), Caddy(:8080, HTTP only), Ollama(:11434), AI Gateway(:8080), frontend(:3000) + - NFS 마운트: /mnt/nas/Document_Server → NAS /volume4/Document_Server + - 외부 접근: document.hyungi.net (Caddy 프록시) + +Mac mini M4 Pro (AI 서버): + - MLX Server: http://100.76.254.116:8800/v1/chat/completions (Qwen3.5-35B-A3B) + - Roon Core +``` + +GPU 서버 Tailscale IP도 추가. AI 모델 구성 섹션에서 primary endpoint 변경 반영. + +#### docs/architecture.md — 섹션 3 (인프라 역할 분담) 갱신 + +Mac mini가 애플리케이션 서버 → GPU 서버가 메인 서버로 변경. +Mac mini는 AI 서버(MLX)만 담당하는 것으로 변경. +아스키 다이어그램 업데이트. + +#### docs/deploy.md — GPU 서버 기준 배포 가이드로 변경 + +- 전제조건: NFS 마운트 (/mnt/nas/Document_Server) +- clone 경로, docker compose 명령 등 GPU 서버 기준으로 변경 +- pg_dump/pg_restore 마이그레이션 절차 추가 + +#### docs/devlog.md — GPU 이전 기록 추가 + +Phase 1~2는 이미 기록됨. 아래 추가: + +1. Phase 3 완료 기록 (자동화 이전: law_monitor, mailplus_archive, daily_digest, automation_state, APScheduler cron) — 기록 안 되어 있으면 추가 +2. Phase 4 완료 기록 (SvelteKit UI: 로그인, 대시보드, 문서 탐색/검색, Inbox, 설정, Docker 통합) — 기록 안 되어 있으면 추가 +3. GPU 서버 이전 기록 (NFS 전환, docker-compose 통합, AI 엔드포인트 변경, Caddy HTTP only 구조) + +### 작업 6: gpu-server/docker-compose.yml 비활성화 + +- 파일 상단에 주석 추가: "# 이 파일은 더 이상 사용하지 않음. 루트 docker-compose.yml로 통합됨." +- 또는 gpu-server/docker-compose.yml.bak으로 리네임 + +--- + +## 작업 순서 (추천) + +1. docker-compose.yml 통합 (작업 1) +2. config.yaml 변경 (작업 2) +3. credentials.env.example 갱신 (작업 3) +4. gpu-server/docker-compose.yml 비활성화 (작업 6) +5. 문서 업데이트 (작업 5) — CLAUDE.md, architecture.md, deploy.md, devlog.md +6. Caddyfile 확인 (작업 4) + +## 주의사항 + +- credentials.env 자체는 git에 올리지 않음 (.gitignore). example만 수정. +- Mac mini Tailscale IP `100.76.254.116`은 config.yaml에 직접 기입 (credentials.env에서 변수로 관리해도 됨) +- NAS 경로: Docker 컨테이너 내부에서는 `/documents`로 접근 (기존과 동일) +- GPU 서버 로컬 IP `192.168.1.186`은 NFS 마운트에만 사용, Docker 설정에는 불필요 diff --git a/gpu-server/docker-compose.yml b/gpu-server/docker-compose.yml index 7f579fd..02a0ecf 100644 --- a/gpu-server/docker-compose.yml +++ b/gpu-server/docker-compose.yml @@ -1,4 +1,7 @@ -version: '3.8' +# ═══════════════════════════════════════════════════ +# 이 파일은 더 이상 사용하지 않음. +# 루트 docker-compose.yml로 통합됨 (2026-04-03). +# ═══════════════════════════════════════════════════ services: ollama: