# 쟁승메이드 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`와 분리된 별도 배포 방식 차용**(이 사이트 전용 파이프라인). 구체 방식(예: 로컬 빌드→이미지 레지스트리 push→NAS pull, 또는 Gitea Actions/별도 webhook 수신기)은 계획 단계에서 확정.

### 4.5 네트워킹·SSL·백업
- DNS: `jaengseung-made.com` → NAS 공인 IP(DDNS). 443 포워딩. Let's Encrypt 자동 갱신.
- 백업: Postgres 일일 `pg_dump` cron + 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. 결정 사항 (확정)
1. 접근 = **A. 풀 self-host**.
2. GoTrue 메일 = **Resend SMTP 연결**(이메일 확인 유지) 기본, 운영 복잡 시 확인 비활성 대안.
3. 컷오버 = **Vercel/Supabase 병행 후 DNS 전환**, DNS 롤백.
4. Phase 0 = **NAS 리소스 실측을 첫 관문**(RAM은 해소, CPU/디스크/부하 확인).
5. 빌드 = **로컬 빌드 후 NAS 배포**(Celeron 빌드 금지). 배포 파이프라인은 **기존 webpage-deployer와 분리된 별도 방식**(구체안 계획에서 확정).

## 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 백업·운영·클라우드 해지.
