6.5 KiB
6.5 KiB
쟁승메이드 NAS 풀 self-host 전환 — 설계 (Spec)
상태: 설계 합의 완료 (2026-06-02). 다음 단계 = 단계별 마이그레이션 계획(writing-plans). 목표: Vercel + Supabase(클라우드) + GitHub public → NAS 자체 호스팅 + self-host Supabase 스택 + 개인 Gitea(비공개) 로 전환.
1. 배경 & 동기
- 결정 동기 (박재오, 2026-06-02): ① 데이터 주권·통합 운영(사주 등 민감 데이터를 내 NAS에 보관, 기존 NAS 서비스들과 한곳에서 관리), ② 벤더 종속 회피(Vercel/Supabase 정책·가격·계정정지 리스크 탈피). 비용 절감·학습은 부차.
- 접근 확정: A. 풀 self-host — Supabase 스택을 통째로 NAS에 올려 코드 변경을 최소화. (대안 B 슬림 self-host=Auth 전면 재작성 비용 과다, C 하이브리드=구조적으로 어정쩡 → 모두 기각.)
- 핵심 통찰: Gitea 이전과 NAS self-host는 한 묶음 — Vercel은 Gitea 자동배포를 지원하지 않으므로 Gitea 이전 시 self-host가 자연 귀결.
2. 현재 결합도 (이전 비용 분석)
| 의존 | 결합도 | 비고 |
|---|---|---|
| Supabase Auth | 매우 높음 | 이메일+OAuth 로그인, 세션 미들웨어(utils/supabase/middleware.ts), 13개 API가 auth.getUser(), RLS가 auth.uid() 기반 |
| Supabase DB(Postgres)+RLS | 높음 | 전 기능 의존, Postgres 자체는 이식 가능 |
| Supabase Storage | 중간 | pack-files 서명 URL (/api/packs/sign-link, list-mine, lib/supabase/pack-files.ts) |
| Vercel | 낮음 | 전 route runtime='nodejs' → next start self-host 가능. maxDuration=60은 app/api/saju/analyze 1곳(Vercel 전용) |
| Gemini·Portone·Resend | 없음 | 외부 API — 호스팅 위치 무관 |
→ 풀 self-host(A)의 핵심 이점: supabase-js의 endpoint(URL)와 키만 self-host로 교체하면 Auth·RLS·storage 코드가 거의 그대로 동작.
3. 목표 아키텍처 (NAS docker-compose)
[인터넷] → DDNS(gahusb.synology.me → jaengseung-made.com)
→ 포트포워딩 443 → nginx (기존, SSL 종단 · Let's Encrypt)
├─ / , /api/* → next-app (next start, output: standalone)
└─ /auth /rest /storage → supabase-kong (게이트웨이)
├─ gotrue (Auth: 이메일 + Google OAuth)
├─ postgrest (supabase-js REST 대상)
├─ storage-api (pack-files)
└─ postgres (전용 인스턴스 — 기존 NAS 서비스 DB와 분리)
- 격리: 기존 NAS 스택(redis·lotto·stock·music-lab 등 15+ 컨테이너)과 별도 compose 프로젝트. Postgres는 전용 컨테이너로 기존 서비스 DB와 분리.
- 리소스: NAS RAM 18,432MB(18GB) 확보됨 → 메모리 차단요소 해소. CPU(Celeron J4025 2코어)·디스크·동시 부하는 Phase 0에서 실측.
4. 컴포넌트 설계
4.1 코드 변경 (최소)
.env:NEXT_PUBLIC_SUPABASE_URL→ NAS 도메인,ANON_KEY/SERVICE_ROLE_KEY→ self-host 발급 JWT 키로 교체.next.config.ts:output: 'standalone'추가, 빌드에sharp(next/image) 포함.maxDuration=60제거(self-host에선 무의미 — Node 프로세스가 직접 처리).- 그 외 앱 로직(supabase-js 호출, RLS 의존 코드)은 무수정 목표.
4.2 데이터 마이그레이션
pg_dump(Supabase 클라우드 전체:auth스키마 +public테이블 + RLS 정책 +storage메타) → NAS Postgres restore.- Storage 객체(pack-files 실파일) 복사.
- 검증: 테이블별 행수 대조, 로그인/세션/RLS 동작, 서명 URL 다운로드, 결제·구독 흐름.
4.3 Auth (GoTrue)
- Google OAuth: redirect URL에 NAS 도메인 추가, 세션 쿠키 도메인 정렬.
- GoTrue 메일 발송: 기존 Resend를 SMTP 자격증명으로 연결(또는 이메일 확인 비활성 정책 — Phase에서 택1).
- JWT secret 자체 발급 → anon/service_role 키 재생성.
4.4 배포 파이프라인 (GitHub public → Gitea)
- 코드 저장소를 개인 Gitea로 이전(비공개).
- 빌드는 로컬 PC에서 수행(NAS Celeron 빌드 금지 규칙 — workspace CLAUDE.md). standalone 산출물/도커 이미지를 NAS로 전송.
- 기존
webpage-deployer(Gitea webhook 수신) 패턴 재사용 → 컨테이너 재시작.
4.5 네트워킹·SSL·백업
- DNS:
jaengseung-made.com→ NAS 공인 IP(DDNS). 443 포워딩. Let's Encrypt 자동 갱신. - 백업: Postgres 일일
pg_dumpcron + Storage 백업. 기본 모니터링(헬스체크).
4.6 컷오버·롤백
- Vercel+Supabase를 병행 운영하며 NAS 스택 안정화 → 데이터 최종 동기 → DNS 전환으로 컷오버.
- 문제 발생 시 DNS만 되돌리면 즉시 롤백. NAS 안정 확인 후 클라우드 자원 해지.
5. 선결 차단요소 & 리스크
| 항목 | 상태 | 대응 |
|---|---|---|
| NAS RAM | ✅ 해소 (18GB) | — |
| CPU/디스크/동시부하 | 🔍 Phase 0 실측 | 부족 시 컨테이너 리소스 제한·우선순위 조정 |
| 가용성 다운그레이드 | ⚠️ 수용 | 공개 사이트가 가정 전기·인터넷·동적IP·단일HW·DDoS에 노출. 동기(주권)상 수용하되 백업·DNS 롤백으로 완화 |
| GoTrue 메일/OAuth 전환 | 리스크 | Resend SMTP 연결 + OAuth redirect 검증 |
| self-host Supabase 운영 | 지속 부담 | 버전 업그레이드·백업 루틴 문서화 |
6. 결정 사항 (확정)
- 접근 = A. 풀 self-host.
- GoTrue 메일 = Resend SMTP 연결(이메일 확인 유지) 기본, 운영 복잡 시 확인 비활성 대안.
- 컷오버 = Vercel/Supabase 병행 후 DNS 전환, DNS 롤백.
- Phase 0 = NAS 리소스 실측을 첫 관문(RAM은 해소, CPU/디스크/부하 확인).
- 빌드 = 로컬 빌드 후 NAS 배포(Celeron 빌드 금지).
7. 범위 밖 (Non-goals)
- 외부 API(Gemini/Portone/Resend) 자체 대체 — 하지 않음(호스팅 위치 무관).
- 기존 NAS 다른 서비스(lotto/stock 등) 변경 — 무관.
- 사주 별도 도메인 분리(P5, 도메인 미구매) — 별개 트랙.
8. 다음 단계
이 spec 승인 후 writing-plans로 단계별 마이그레이션 계획 작성:
- Phase 0 리소스 실측 → Phase 1 NAS Supabase 스택 기동 → Phase 2 데이터 이전·검증 → Phase 3 Next self-host·코드 env 전환 → Phase 4 Gitea 이전·배포 파이프라인 → Phase 5 도메인/SSL/병행→컷오버 → Phase 6 백업·운영·클라우드 해지.