gahusb 5844567048 fix(packs-lab): PACK_BASE_DIR을 환경변수로 — 컨테이너 마운트 경로와 routes 정합성 확보
이전 docker-compose는 컨테이너 내부 /app/data/packs로 마운트하지만 routes.py는
/volume1/docker/webpage/media/packs를 하드코딩하고 있어 mismatch였다.

- routes.py: PACK_BASE_DIR = Path(os.getenv("PACK_BASE_DIR", "/app/data/packs"))
- docker-compose: PACK_BASE_DIR env 추가 + volume 마운트가 같은 경로 사용
- .env.example: PACK_BASE_DIR 신규 명시 (마운트 경로와 반드시 일치 안내)
2026-05-06 01:40:07 +09:00
2026-05-01 11:36:56 +09:00

web-backend

Synology NAS 기반 개인 웹 플랫폼 백엔드 모노레포. 로또 분석, 주식 포트폴리오, AI 음악 생성, 블로그 마케팅, 부동산 청약, AI 에이전트 오피스, 여행 앨범을 하나의 Docker Compose 스택으로 운영한다.


서비스 구성

┌──────────────────────────────────────────────────────────────────────┐
│  lotto-frontend (Nginx:8080)                                          │
│  ├── 정적 SPA 서빙 (React + Vite)                                     │
│  └── API 리버스 프록시                                                 │
│       ├── /api/                  → lotto-backend:8000  (로또·블로그·투두)│
│       ├── /api/stock/, /trade/   → stock-lab:8000                     │
│       ├── /api/portfolio         → stock-lab:8000                     │
│       ├── /api/music/            → music-lab:8000                     │
│       ├── /api/blog-marketing/   → blog-lab:8000                      │
│       ├── /api/realestate/       → realestate-lab:8000                │
│       ├── /api/agent-office/     → agent-office:8000 (+ WebSocket)    │
│       ├── /api/travel/           → travel-proxy:8000                  │
│       ├── /media/music/…          (nginx 직접 서빙, 생성 오디오)        │
│       ├── /media/travel/…         (nginx 직접 서빙, 사진/썸네일)        │
│       └── /webhook               → deployer:9000                      │
└──────────────────────────────────────────────────────────────────────┘
컨테이너 포트 역할
lotto-backend 18000 로또 데이터 수집·분석·추천 + 블로그·투두 API
stock-lab 18500 주식 뉴스·AI 요약·KIS 실계좌·포트폴리오·자산 추적
music-lab 18600 AI 음악 생성 (Suno + 로컬 MusicGen 듀얼 프로바이더)
blog-lab 18700 블로그 마케팅 수익화 (키워드→글 생성→리뷰→발행)
realestate-lab 18800 청약 공고 자동 수집·프로필 매칭
agent-office 18900 AI 에이전트 가상 오피스 (WebSocket + 텔레그램 봇)
travel-proxy 19000 여행 사진 API + 온디맨드 썸네일
lotto-frontend 8080 SPA 서빙 + 리버스 프록시
webpage-deployer 19010 Gitea Webhook → 자동 배포

디렉토리 구조

web-backend/
├── backend/              # lotto-backend (로또·블로그·투두)
├── stock-lab/            # 주식·포트폴리오
├── music-lab/            # AI 음악 생성
├── blog-lab/             # 블로그 마케팅 파이프라인
├── realestate-lab/       # 청약 자동 수집·매칭
├── agent-office/         # AI 에이전트 오피스 (WS + 텔레그램)
├── travel-proxy/         # 여행 사진 + 썸네일
├── deployer/             # Gitea Webhook 수신 → 자동 배포
├── nginx/default.conf    # 리버스 프록시 + SPA + 캐시
├── scripts/              # deploy.sh, deploy-nas.sh, healthcheck.sh
├── docker-compose.yml
├── .env.example
└── CLAUDE.md             # Claude Code 작업용 상세 컨텍스트

빠른 시작 (로컬 개발)

cp .env.example .env
docker compose up -d

curl http://localhost:18000/health
curl http://localhost:18500/health
서비스 로컬 URL
Frontend + API http://localhost:8080
lotto-backend http://localhost:18000
stock-lab http://localhost:18500
music-lab http://localhost:18600
blog-lab http://localhost:18700
realestate-lab http://localhost:18800
agent-office http://localhost:18900
travel-proxy http://localhost:19000

서비스별 기능

1. lotto-backend (/api/)

로또 당첨번호 수집·통계 분석·몬테카를로 시뮬레이션 기반 추천 + 투두·블로그 CRUD.

  • 로또: 당첨번호 조회, 5종 통계 분석, 시뮬레이션 최적 번호(best_picks 20쌍), 통계/히트맵/스마트/배치 추천, 전략 가중치(EMA+Softmax), 구매 이력 관리
  • 추천 이력: 즐겨찾기·태그·메모 관리
  • 투두리스트: UUID PK, 상태(todo/in_progress/done)
  • 블로그: 일기형 포스트 (tags JSON 배열, date DESC)

스케줄러

  • 09:10 / 21:10 — 당첨번호 동기화 + 추천 채점
  • 00:05, 04:05, 08:05, 12:05, 16:05, 20:05 — 몬테카를로 시뮬레이션 (후보 20,000 → 상위 100 → best_picks 20쌍 교체)

2. stock-lab (/api/stock/, /api/trade/, /api/portfolio)

주식 뉴스 스크래핑 + LLM 요약 + KIS 실계좌 연동 + 포트폴리오·자산 스냅샷.

  • 뉴스: 네이버 증권 + 해외 사이트 크롤링, LLM 기반 한국어 요약
  • 실계좌: Windows AI 서버(192.168.45.59:8000) 프록시 → KIS Open API (잔고/주문)
  • 포트폴리오: 종목·예수금·매도 히스토리 관리, 현재가 자동 조회
  • 자산 스냅샷: 평일 15:40 자동 저장 (KRX 공휴일 판별, holidays.json 매년 갱신)

LLM provider 전환LLM_PROVIDER 환경변수

  • claude (기본): Anthropic Messages API (claude-haiku-4-5)
  • ollama: Windows AI 서버 Ollama (qwen3:14b)

현재가 조회: 네이버 모바일 API → HTML 파싱 폴백, 3분 TTL 메모리 캐시

3. music-lab (/api/music/)

듀얼 프로바이더 AI 음악 생성.

  • Suno (suno): REST API 연동, 보컬·가사·인스트루멘탈. 1회 요청 시 2개 variation 생성, 곡 연장, 보컬 분리, WAV 변환, 12스템 분리, 뮤직비디오, AI Cover 등 풀 스위트 지원
  • 로컬 MusicGen (local): Windows AI PC(RTX 5070 Ti, 16GB VRAM) 인스트루멘탈 전용
  • 라이브러리: 생성 파일은 /app/data/music/에 저장되고 Nginx가 /media/music/으로 직접 서빙
  • 가사 도구: 저장·편집·타임스탬프 기반 가라오케 동기

4. blog-lab (/api/blog-marketing/)

블로그 마케팅 수익화 4단계 파이프라인 (draft → marketed → reviewed → published).

리서치(Naver Search + 상위 블로그 본문 크롤링)
  → 작가(AI 초안 생성)
    → 마케터(전환율 강화 + 브랜드 링크 삽입)
      → 평가자(6기준×10점, 42/60 통과 시 published)
  • AI 엔진: Claude API (claude-sonnet-4-20250514)
  • 키워드 분석: 네이버 검색(블로그+쇼핑) API + 경쟁도/기회 점수
  • 수익 추적: 포스트별 월간 클릭/구매/수익 기록
  • 프롬프트 템플릿: DB에 저장 → 코드 배포 없이 수정 가능

5. realestate-lab (/api/realestate/)

공공데이터포털 청약홈 API 연동 + 프로필 기반 자동 매칭.

  • 공고 수집: 09:00 매일 자동 (DATA_GO_KR_API_KEY 필요)
  • 상태 갱신 + 재매칭: 00:00 매일 자동
  • 프로필 매칭: 지역·주택형·소득·부양가족 등으로 점수화, 신규 매칭 알림
  • 대시보드: 진행 중 공고수, 신규 매칭수, 다가오는 일정 요약

6. agent-office (/api/agent-office/)

AI 에이전트 가상 오피스 — 2D 픽셀아트 사무실에서 4명의 에이전트가 실제 작업을 수행한다.

  • 아키텍처: stock-lab / music-lab / blog-lab / realestate-lab 기존 API를 서비스 프록시로 호출 (직접 DB 접근 없음)
  • FSM 상태: idle → working → waiting(승인 대기) → reporting → break
  • 실시간 동기화: WebSocket /api/agent-office/ws (init, agent_state, task_complete, command_result)
  • 텔레그램 연동: 양방향 알림 + 인라인 키보드 승인
    • 봇이 작업 결과를 텔레그램으로 푸시, 명령은 텔레그램에서 바로 에이전트에 전달
    • Webhook 검증 후 chat.id 기준 라우팅

에이전트 구성

에이전트 스케줄 승인 주요 기능
📈 주식 트레이더 (stock) 08:00 매일 뉴스 요약 (LLM) → 텔레그램 아침 브리핑, 종목 알람 등록
🎵 음악 프로듀서 (music) 수동 트리거 작곡 프롬프트 수신 → 승인 → Suno API 작곡 → 트랙 푸시
✍️ 블로그 마케터 (blog) 10:00 매일 발행 트렌드 키워드 1개 선택 → 리서치→작가→마케터→평가 자동 실행 → 점수·본문을 텔레그램 승인 요청 → 승인 시 published 전환, 거절 시 재생성
🏢 청약 애널리스트 (realestate) 09:15 매일 realestate-lab 수집 트리거 → 신규 매칭 상위 5건 + 대시보드 요약을 텔레그램 리포트 (읽음 처리 자동)

에이전트별 명령

Stockfetch_news, list_alerts, add_alert, test_telegram Musiccompose (승인 필요), credits Blogresearch {keyword}, add_trend_keyword, list_trend_keywords Realestatefetch_matches, dashboard

스케줄러 잡

  • 07:00 월요일 — Lotto: AI 큐레이터 브리핑 (5세트 + 내러티브)
  • 07:30 — Stock: 뉴스 요약
  • 09:15 — Realestate: 매칭 리포트
  • 10:00 — Blog: 자동 파이프라인 (리서치→생성→리뷰→승인 대기)
  • 60초 interval — 유휴 에이전트 휴식 체크

7. travel-proxy (/api/travel/)

여행 사진 API + SQLite 인덱스 + 온디맨드 썸네일 + 지역 관리.

  • 원본: /data/travel/ (RO 마운트)
  • 썸네일: 480×480 Pillow 리사이징, /data/thumbs/ 영구 캐시 (tmp → rename 원자성 보장)
  • DB: /data/thumbs/travel.db (photos, album_covers 테이블)
  • 메타: region_map.json (RO) + region_map_extra.json (RW 오버라이드) + regions.geojson
  • 지역 관리: 앨범 지역 변경, 커스텀 지역 생성, 지도 핀 좌표 지정
  • 데이터 흐름: 수동 sync → 폴더 스캔 → SQLite 인덱싱 + 썸네일 일괄 생성

8. deployer (/webhook)

Gitea Webhook 수신 → NAS 자동 배포.

  • HMAC SHA256 서명 검증 (compare_digest, WEBHOOK_SECRET)
  • 수신 즉시 200 응답 후 BackgroundTask로 배포
  • 배포 스크립트: git pull.releases/ 백업 → rsyncdocker compose up -d --buildchown PUID:PGID
  • 타임아웃 10분

핵심 로직

몬테카를로 시뮬레이션 (lotto-backend)

역대 당첨번호 분석 → 번호별 가중치 산출
→ 가중 확률 샘플링으로 후보 20,000개 생성
→ 5가지 기법으로 각 조합 점수화
→ 상위 100개 DB 저장 → best_picks 20개 교체
기법 가중치 내용
빈도 Z-score 25% 번호 출현 빈도의 표준편차
조합 지문 30% 합계 정규분포 + 홀짝 비율 + 구간분포
갭 분석 20% 마지막 출현 이후 경과 회차
공동 출현 15% 번호 쌍 동시 출현 빈도
다양성 10% 연속번호·범위·구간 커버리지

LLM 요약 provider 추상화 (stock-lab)

ai_summarizer.py는 provider 분리 구조. summarize_news(articles) 시그니처는 provider와 무관하게 고정.

  • _summarize_with_claude: Anthropic Messages API 직접 호출 (httpx, SDK 의존성 없음)
  • _summarize_with_ollama: Ollama /api/generate (타임아웃 180s, qwen3:14b 첫 로드 대응)
  • 실패 시 LLMError (구 OllamaError alias 유지)

총 자산 스냅샷 (stock-lab)

평일 15:40 자동 실행 → holidays.json으로 공휴일 스킵 → 포트폴리오 현재가 조회 + 예수금 합계 → asset_snapshots upsert (date UNIQUE).

에이전트 FSM + WS 동기화 (agent-office)

DB에 저장된 에이전트 상태가 바뀔 때마다 websocket_manager가 전체 클라이언트에 브로드캐스트. 텔레그램 봇은 waiting 상태 작업에 인라인 키보드를 붙여 승인 요청. 승인/거부 결과가 DB → WS → 프론트로 전파.


자동 배포

git push → Gitea → X-Gitea-Signature (HMAC SHA256)
  → deployer:9000/webhook (서명 검증, compare_digest)
  → BackgroundTask: scripts/deploy.sh (10분 타임아웃)
      1. git pull
      2. .releases/{timestamp}/ 백업
      3. rsync (repo → runtime)
      4. docker compose up -d --build
      5. chown PUID:PGID

프론트엔드는 자동 배포 안 됨 — 로컬 빌드 후 NAS에 수동 업로드 (scripts/deploy.bat --frontend)


데이터베이스

각 서비스는 독립 SQLite DB를 /app/data/ 볼륨에 저장.

DB 소유 서비스 주요 테이블
lotto.db lotto-backend draws, recommendations, simulation_runs/candidates, best_picks, purchase_history, strategy_performance/weights, weekly_reports, lotto_briefings, todos, blog_posts
stock.db stock-lab articles, portfolio, broker_cash, asset_snapshots, sell_history
music.db music-lab music_tasks, music_library (provider, lyrics, image_url, suno_id, file_hash, cover_images, wav_url, video_url, stem_urls)
blog_marketing.db blog-lab keyword_analyses, blog_posts, brand_links, commissions, generation_tasks, prompt_templates
realestate.db realestate-lab announcements, announcement_models, user_profile, match_results, collect_log
agent_office.db agent-office agent_config, agent_tasks, agent_logs, telegram_state, conversation_messages
travel.db travel-proxy photos (album, filename, mtime, has_thumb), album_covers

환경변수

# 경로
RUNTIME_PATH=.
REPO_PATH=.
FRONTEND_PATH=./frontend/dist
PHOTO_PATH=./mock_data/photos

# NAS 파일 권한
PUID=1000
PGID=1000

# 외부 서비스
WINDOWS_AI_SERVER_URL=http://192.168.45.59:8000
WEBHOOK_SECRET=your_secret_here

# LLM (stock-lab, blog-lab, agent-office 공통)
ANTHROPIC_API_KEY=sk-ant-...
ANTHROPIC_MODEL=claude-haiku-4-5-20251001
LLM_PROVIDER=claude              # claude | ollama
OLLAMA_URL=http://192.168.45.59:11435
OLLAMA_MODEL=qwen3:14b

# music-lab
SUNO_API_KEY=
MUSIC_AI_SERVER_URL=
MUSIC_MEDIA_BASE=/media/music

# blog-lab
NAVER_CLIENT_ID=
NAVER_CLIENT_SECRET=

# realestate-lab
DATA_GO_KR_API_KEY=

# agent-office
TELEGRAM_BOT_TOKEN=
TELEGRAM_CHAT_ID=
TELEGRAM_WEBHOOK_URL=
STOCK_LAB_URL=http://stock-lab:8000
MUSIC_LAB_URL=http://music-lab:8000
BLOG_LAB_URL=http://blog-lab:8000
REALESTATE_LAB_URL=http://realestate-lab:8000

인프라

항목
장비 Synology NAS (Intel Celeron J4025, 18GB RAM)
Docker Synology Container Manager
Git 서버 Gitea (NAS 내부 self-hosted, gahusb.synology.me)
AI 서버 Windows PC (192.168.45.59) — RTX 5070 Ti (16GB VRAM) + Ollama + MusicGen
Python 3.12 (slim 기반 이미지)
DB SQLite (볼륨 마운트로 영속 저장)

주의사항

  • .env 파일 — 절대 커밋 금지. .env.example만 레포에 포함
  • Nginx trailing slash/api/portfolio는 두 location 블록으로 처리 (trailing slash 유무 모두 매칭)
  • 라우트 순서DELETE /api/todos/done/api/todos/{id} 보다 먼저 등록 필수 (FastAPI prefix 매칭)
  • 캐시 전략index.html: no-store / assets/: 1년 immutable
  • PUID/PGID — travel-proxy는 NAS 파일 권한을 위해 환경변수 주입 필수
  • 공휴일 목록stock-lab/app/holidays.json 매년 수동 갱신 (KRX 기준)
  • Windows AI 서버 IP192.168.45.59 공유기 DHCP 고정 예약. Synology Tailscale은 userspace 모드라 TCP 불가 → 로컬 IP 사용
  • Suno CDNcdn1.suno.ai URL은 임시 만료 → 생성 즉시 로컬 다운로드 필수
  • LLM provider 롤백 — Claude API 장애 시 .envLLM_PROVIDER=ollama로 전환 후 docker compose up -d
  • 시뮬레이션 교체 방식best_picks는 교체형 (is_active=0 비활성화 후 신규 입력)

참고 문서

  • CLAUDE.md — Claude Code 작업용 상세 컨텍스트 (API 전체 목록, 테이블 스키마 등)
  • docs/ — 서비스별 기획·설계 문서
Description
웹페이지 백엔드 저장소
Readme 3.5 MiB
Languages
Python 98.7%
Shell 0.8%
Dockerfile 0.4%
Jinja 0.1%