README.md / STATUS.md가 blog-lab을 운영 중인 18700 포트 컨테이너로 설명하고 insta-lab/personal/packs-lab을 누락했던 문제 정리. CLAUDE.md를 source of truth로 다음을 갱신: - 컨테이너 표 (11개로 정합화) - 디렉토리 구조 (insta-lab/personal/packs-lab 추가) - 빠른 시작 URL 표 - blog-lab 섹션 → insta-lab 파이프라인 설명 - agent-office 표 (InstaAgent + YouTubeResearcher 반영) - 스케줄러 잡 목록 (09:00 Insta trends, 09:30 Insta extract, 16:30 screener 등) - DB 표 (insta.db + personal.db + Supabase pack_files 추가) - .env 예시 (YOUTUBE_DATA_API_KEY, ADMIN_API_KEY, INSTA_LAB_URL 등) - STATUS 최근 작업: 2026-05-15~17 인스타 + 보안 fix 이력
18 KiB
web-backend
Synology NAS 기반 개인 웹 플랫폼 백엔드 모노레포. 로또 분석, 주식 포트폴리오, AI 음악 생성, 인스타 카드 피드, 부동산 청약, AI 에이전트 오피스, 여행 앨범, 개인 서비스(포트폴리오·블로그·투두), NAS 자료 다운로드 자동화를 하나의 Docker Compose 스택으로 운영한다.
서비스 구성
┌──────────────────────────────────────────────────────────────────────┐
│ frontend (Nginx:8080) │
│ ├── 정적 SPA 서빙 (React + Vite) │
│ └── API 리버스 프록시 │
│ ├── /api/ → lotto:8000 (로또) │
│ ├── /api/stock/, /trade/ → stock:8000 │
│ ├── /api/portfolio → stock:8000 │
│ ├── /api/music/ → music-lab:8000 │
│ ├── /api/insta/ → insta-lab:8000 │
│ ├── /api/realestate/ → realestate-lab:8000 │
│ ├── /api/agent-office/ → agent-office:8000 (+ WebSocket) │
│ ├── /api/profile/, /todos, /blog/ → personal:8000 │
│ ├── /api/packs/ → packs-lab:8000 (HMAC + 5GB upload) │
│ ├── /api/travel/ → travel-proxy:8000 │
│ ├── /media/music/, /media/videos/ (nginx 직접 서빙, 미디어) │
│ ├── /media/travel/… (nginx 직접 서빙, 사진/썸네일) │
│ └── /webhook → deployer:9000 │
└──────────────────────────────────────────────────────────────────────┘
| 컨테이너 | 포트 | 역할 |
|---|---|---|
lotto |
18000 | 로또 데이터 수집·분석·추천 API |
stock |
18500 | 주식 뉴스·AI 요약·KIS 실계좌·포트폴리오·자산 추적 |
music-lab |
18600 | AI 음악 생성 (Suno + 로컬 MusicGen 듀얼 프로바이더) + YouTube 수익화 |
insta-lab |
18700 | 인스타 카드 피드 자동 생성 (뉴스→키워드→10페이지 카드, Playwright) |
realestate-lab |
18800 | 청약 공고 자동 수집·5티어 매칭·신규 매칭 push |
agent-office |
18900 | AI 에이전트 가상 오피스 (WebSocket + 텔레그램 봇) |
personal |
18850 | 개인 서비스 — 포트폴리오·블로그·투두 통합 |
packs-lab |
18950 | NAS 자료 다운로드 자동화 (DSM 공유 링크 + 5GB 청크 업로드) |
travel-proxy |
19000 | 여행 사진 API + 온디맨드 썸네일 |
frontend |
8080 | SPA 서빙 + 리버스 프록시 |
webpage-deployer |
19010 | Gitea Webhook → 자동 배포 |
디렉토리 구조
web-backend/
├── lotto/ # 로또 추천·통계·시뮬레이션
├── stock/ # 주식·포트폴리오·KIS 연동
├── music-lab/ # AI 음악 생성 + YouTube 수익화
├── insta-lab/ # 인스타 카드 피드 자동 생성 (Playwright)
├── realestate-lab/ # 청약 자동 수집·5티어 매칭
├── agent-office/ # AI 에이전트 오피스 (WS + 텔레그램)
├── personal/ # 포트폴리오·블로그·투두 통합
├── packs-lab/ # NAS 자료 다운로드 자동화 (HMAC + Supabase)
├── 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 | http://localhost:18000 |
| stock | http://localhost:18500 |
| music-lab | http://localhost:18600 |
| insta-lab | http://localhost:18700 |
| realestate-lab | http://localhost:18800 |
| personal | http://localhost:18850 |
| agent-office | http://localhost:18900 |
| packs-lab | http://localhost:18950 |
| travel-proxy | http://localhost:19000 |
서비스별 기능
1. lotto-backend (/api/)
로또 당첨번호 수집·통계 분석·몬테카를로 시뮬레이션 기반 추천 + 투두·블로그 CRUD.
- 로또: 당첨번호 조회, 5종 통계 분석, 시뮬레이션 최적 번호(
best_picks20쌍), 통계/히트맵/스마트/배치 추천, 전략 가중치(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 (/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. insta-lab (/api/insta/)
인스타그램 카드 피드 자동 생성 — 뉴스 모니터링 → 키워드 추출 → 10페이지 카드 카피·PNG 렌더 → 텔레그램 푸시 → 사용자 수동 업로드.
NAVER 뉴스 + YouTube 인기 (외부 트렌드)
→ 카테고리별 빈도 + Claude Haiku 정제 → 트렌딩 키워드
→ 사용자가 키워드 선택
→ Claude Sonnet으로 10페이지 카피 추론 (커버 1 + 본문 8 + CTA 1)
→ Jinja2 + Playwright 1080×1350 PNG 10장 렌더
→ 텔레그램 미디어 그룹 + 추천 캡션·해시태그
- AI 엔진: Claude Sonnet (카피) + Claude Haiku (키워드 분류)
- 데이터 소스: NAVER 뉴스 검색 + YouTube Data API v3 mostPopular(KR)
- 카테고리 가중치: 사용자가 economy/psychology/celebrity 등 카테고리별 가중치 설정 → 자동 추출 비율에 반영
- 카드 디자인:
insta-lab/app/templates/default/card.html.j2— 사용자가 자유 수정 (Tailwind 등) - 프롬프트 템플릿: 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 / music-lab / insta-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 작곡 → 트랙 푸시 |
🎴 인스타 큐레이터 (insta) |
09:00 / 09:30 매일 | — | 09:00 외부 트렌드(NAVER + YouTube) 수집 → 09:30 가중치 기반 키워드 추출 → 텔레그램 후보 5개씩 카테고리당 인라인 버튼 푸시 → 사용자 선택 시 카드 10장 미디어 그룹 |
🏢 청약 애널리스트 (realestate) |
realestate-lab push trigger | — | realestate-lab이 신규 매칭 발견 시 push → 인라인 [북마크] 버튼 포함 텔레그램 알림 |
🎬 YouTube 리서처 (youtube) |
09:00 매일 | — | 한국 YouTube 트렌딩 + Google Trends + Billboard → music-lab market_trends push |
에이전트별 명령
Stock — fetch_news, list_alerts, add_alert, test_telegram
Music — compose (승인 필요), credits
Insta — extract, render <keyword_id>, collect_trends
Realestate — fetch_matches, dashboard
YouTube — research {countries: [...]}
스케줄러 잡
- 07:00 월요일 — Lotto: AI 큐레이터 브리핑 (5세트 + 내러티브)
- 07:30 — Stock: 뉴스 요약
- 08:00 평일 — Stock: AI 뉴스 sentiment 분석
- 09:00 — YouTube: 한국 트렌딩 수집
- 09:00 — Insta: 외부 트렌드 수집 (NAVER 인기 + YouTube mostPopular)
- 09:30 — Insta: 키워드 추출 (가중치 적용) + 텔레그램 후보 푸시
- 15:40 평일 — Stock: 총 자산 스냅샷
- 16:30 평일 — Stock: 스크리너 실행
- 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/백업 →rsync→docker compose up -d --build→chown PUID:PGID - 타임아웃 10분
핵심 로직
몬테카를로 시뮬레이션 (lotto-backend)
역대 당첨번호 분석 → 번호별 가중치 산출
→ 가중 확률 샘플링으로 후보 20,000개 생성
→ 5가지 기법으로 각 조합 점수화
→ 상위 100개 DB 저장 → best_picks 20개 교체
| 기법 | 가중치 | 내용 |
|---|---|---|
| 빈도 Z-score | 25% | 번호 출현 빈도의 표준편차 |
| 조합 지문 | 30% | 합계 정규분포 + 홀짝 비율 + 구간분포 |
| 갭 분석 | 20% | 마지막 출현 이후 경과 회차 |
| 공동 출현 | 15% | 번호 쌍 동시 출현 빈도 |
| 다양성 | 10% | 연속번호·범위·구간 커버리지 |
LLM 요약 provider 추상화 (stock)
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(구OllamaErroralias 유지)
총 자산 스냅샷 (stock)
평일 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 | draws, recommendations, simulation_runs/candidates, best_picks, purchase_history, strategy_performance/weights, weekly_reports, lotto_briefings |
stock.db |
stock | 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), video_projects, revenue_records, market_trends, trend_reports |
insta.db |
insta-lab | news_articles, trending_keywords (source 컬럼), card_slates, card_assets, generation_tasks, prompt_templates, account_preferences |
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 |
personal.db |
personal | profile, careers, projects, skills, introductions, todos, blog_posts |
travel.db |
travel-proxy | photos (album, filename, mtime, has_thumb), album_covers |
pack_files (외부 Supabase) |
packs-lab | filename, host_path, mime, byte_size, sha256, deleted_at |
환경변수
# 경로
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, insta-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
# stock admin protection (CODE_REVIEW F2)
ADMIN_API_KEY=
ALLOW_UNAUTHENTICATED_ADMIN=false
# music-lab
SUNO_API_KEY=
MUSIC_AI_SERVER_URL=
MUSIC_MEDIA_BASE=/media/music
# insta-lab + agent-office (NAVER 검색 + YouTube Data API 공유)
NAVER_CLIENT_ID=
NAVER_CLIENT_SECRET=
YOUTUBE_DATA_API_KEY=
# realestate-lab
DATA_GO_KR_API_KEY=
# packs-lab (DSM + Supabase)
DSM_HOST=
DSM_USER=
DSM_PASS=
BACKEND_HMAC_SECRET=
SUPABASE_URL=
SUPABASE_SERVICE_KEY=
PACK_HOST_DIR=/docker/webpage/media/packs # shared folder 시점 (CLAUDE.md F5)
# agent-office
TELEGRAM_BOT_TOKEN=
TELEGRAM_CHAT_ID=
TELEGRAM_WEBHOOK_URL=
STOCK_URL=http://stock:8000
MUSIC_LAB_URL=http://music-lab:8000
INSTA_LAB_URL=http://insta-lab:8000
REALESTATE_LAB_URL=http://realestate-lab:8000
# personal (포트폴리오 편집 인증)
PORTFOLIO_EDIT_PASSWORD=
인프라
| 항목 | 값 |
|---|---|
| 장비 | 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/app/holidays.json매년 수동 갱신 (KRX 기준) - Windows AI 서버 IP —
192.168.45.59공유기 DHCP 고정 예약. Synology Tailscale은 userspace 모드라 TCP 불가 → 로컬 IP 사용 - Suno CDN —
cdn1.suno.aiURL은 임시 만료 → 생성 즉시 로컬 다운로드 필수 - LLM provider 롤백 — Claude API 장애 시
.env의LLM_PROVIDER=ollama로 전환 후docker compose up -d - 시뮬레이션 교체 방식 —
best_picks는 교체형 (is_active=0비활성화 후 신규 입력)
참고 문서
CLAUDE.md— Claude Code 작업용 상세 컨텍스트 (API 전체 목록, 테이블 스키마 등)docs/— 서비스별 기획·설계 문서