chore: remove committed build artifacts and dead files, archive stray root docs
Root had accumulated files that should never have been tracked:
- Committed build output: TaxBaik.Web.*.json (runtimeconfig/deps), and a
225-file root wwwroot/ that duplicated (and was staler than)
TaxBaik.Web/wwwroot/.
- A stale migrations/ (V001-V003 only) superseded by db/migrations/, which
is the directory MigrationRunner and CI actually use.
- An orphaned root appsettings.json (dev DB password + JWT secret) that the
app's content root (TaxBaik.Web/) never actually loads.
- Ad-hoc debug/log scratch files: debug-settings.js, final-test.js,
test-settings.js, settings-page.png, login-test-output.log,
server.{err,out}.log.
- docker-compose.yml, Dockerfile.*, web.config, SERVER_SETUP.sh, deploy.sh,
remote_deploy.sh - none referenced by any .gitea/workflows/*.yml; leftovers
from a Docker/manual-deploy approach superseded by deploy_gb.sh's
systemd + Green-Blue proxy model.
- Tmp/ - screenshots and a scratch html/js, exactly the "temp work
committed to root" problem.
None of this is destroyed - it stays recoverable via git history if ever
needed. Historical root-level docs (BLOG_TEMPLATE.md, DEPLOYMENT_GUIDE.md,
etc.) are moved into docs/archive/ rather than deleted, since docs/INDEX.md
already treats anything outside docs/ as non-canonical reference material.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,777 @@
|
||||
# 블로그 포스트 작성 템플릿
|
||||
|
||||
## 정확성 원칙 (법적 책임 수반)
|
||||
|
||||
블로그는 **사실 기반, 세법 기반, 데이터 기반**이어야 합니다. 추측이나 예상은 법적 문제를 일으킬 수 있습니다.
|
||||
|
||||
### 절대 금지 표현
|
||||
|
||||
- "아마도", "할 것 같다", "추측된다" (추측)
|
||||
- "대략", "정도일 거다", "보통" (예상)
|
||||
- "좋을 것 같다", "나쁠 것 같다" (의견)
|
||||
- 증거 없는 "모두", "항상", "누구나" (일반화)
|
||||
- 출처 없는 통계 ("80% 고객이", "평균 X만 원")
|
||||
|
||||
### 필수 요소
|
||||
|
||||
**1. 세법 기반**:
|
||||
- 모든 주장에 세법/시행령/고시 인용
|
||||
- 조항 명시: "소득세법 제XX조에 따르면"
|
||||
- 최신 기준 명시: "2025년 기준"
|
||||
- 변경사항 반영: "전년도와 다르게..."
|
||||
|
||||
**2. 사실 기반**:
|
||||
- 실제 일어난 고객 사례만 사용
|
||||
- 가정일 경우 명시: "예를 들어, 만약 이렇다면"
|
||||
- 가상 사례는 "예시 사례"라고 명확히
|
||||
- 개인정보는 익명화 (이름, 나이는 일반적인 표현)
|
||||
|
||||
**3. 데이터 기반**:
|
||||
- 객관적 수치만 사용 (국세청 통계, 협회 자료)
|
||||
- 출처 명시: "2025년 세무청 통계에 따르면"
|
||||
- 구체적 금액: "약 50만 원" (범위 표현)
|
||||
- 비교 데이터: "작년 대비 X% 증가"
|
||||
|
||||
**4. 사례 제시 시 확인 사항**:
|
||||
```
|
||||
✅ 실제 고객인가? (공개 가능한 정보만)
|
||||
✅ 세법을 정확하게 적용했는가?
|
||||
✅ 금액 계산이 정확한가?
|
||||
✅ 이 사례가 대표적인가? (극단적 사례면 명시)
|
||||
✅ 다른 고객에게도 적용 가능한가?
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 카테고리 필수 규칙
|
||||
|
||||
**모든 블로그 포스트는 반드시 하나의 카테고리에 할당되어야 합니다. (NOT NULL)**
|
||||
|
||||
### 카테고리별 포스트 배치
|
||||
|
||||
| 카테고리 | 최소 포스트 | 주제 범위 |
|
||||
|---------|-----------|---------|
|
||||
| 사업자 세무 | 3개 | 기장, 세무신고, 부가세, 종합소득세 |
|
||||
| 부동산 세금 | 3개 | 월세, 양도세, 상속세(부동산) |
|
||||
| 종합소득세 | 3개 | 프리랜서, 부업, 경비 처리 |
|
||||
| 부가가치세 | 3개 | 신고, 기한, 간이과세 vs 일반과세 |
|
||||
| 가족자산·증여 | 3개 | 자녀 증여, 상속, 자산 이전 |
|
||||
|
||||
### 카테고리 할당 규칙
|
||||
|
||||
1. **명확한 주제 분류**: 포스트 내용이 카테고리 범위에 명확하게 해당
|
||||
2. **중복 금지**: 한 포스트는 정확히 하나의 카테고리에만 할당
|
||||
3. **균형 배치**: 각 카테고리당 최소 3개씩 (고객 검색 UX)
|
||||
4. **검색 최적화**: 고객이 카테고리로 찾을 때 관련 포스트 3개 이상 노출
|
||||
|
||||
### 카테고리 미할당 시 (오류)
|
||||
- ❌ category_id = NULL (데이터베이스 제약 위반)
|
||||
- ❌ SQL 실행 실패 (NOT NULL 제약)
|
||||
- ❌ 블로그 페이지 노출 불가
|
||||
|
||||
**이 규칙은 모든 포스트 생성/수정 시 필수 준수사항입니다.**
|
||||
|
||||
---
|
||||
|
||||
## 핵심 철학: 고객이 느끼는 여정
|
||||
|
||||
### 1️⃣ 기초: "이 정도는 할 수 있어요"
|
||||
- 고객이 배울 수 있는 기본 개념
|
||||
- 실제 사례로 구체화
|
||||
- 단계별 설명
|
||||
|
||||
### 2️⃣ 현실: "하지만 복잡하네요"
|
||||
- 겹겹이 쌓인 세부사항들
|
||||
- 매년 바뀌는 세법
|
||||
- "이거 일일이 다 챙기기 어렵다"는 느낌
|
||||
|
||||
### 3️⃣ 해결: "세무사와 함께면 괜찮아요"
|
||||
- 디테일 자동 관리
|
||||
- 세법 변화 자동 반영
|
||||
- 고객은 사업에만 집중
|
||||
|
||||
---
|
||||
|
||||
**고객이 글을 읽은 후 느끼는 것**:
|
||||
|
||||
1️⃣ 읽고 나서: "아, 이 정도는 내가 할 수 있겠네"
|
||||
2️⃣ 생각해보니: "근데 이 모든 걸 매년 챙기기는... 힘들겠는데?"
|
||||
3️⃣ 결론: "그럼 전문가 도움을 받는 게 낫겠다"
|
||||
|
||||
→ 자연스럽게 세무사의 필요성을 깨달음 (강요 아님)
|
||||
|
||||
---
|
||||
|
||||
## 템플릿 (복사해서 사용)
|
||||
|
||||
### Step 1: 도입부 (공감)
|
||||
```markdown
|
||||
# [제목]
|
||||
|
||||
"[구체적 상황]?"
|
||||
"많은 [직업]들이 이 상황을 겪습니다."
|
||||
|
||||
→ 독자가 자신의 상황을 발견하도록
|
||||
```
|
||||
|
||||
**예시**:
|
||||
```markdown
|
||||
# 동네 카페 월세 낼 때 세금이 안 나와요 - 정말 그럴까?
|
||||
|
||||
"사업을 시작했는데 세금을 낸 적이 없어요"
|
||||
"많은 소규모 사업자들이 이렇게 생각합니다."
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Step 2: 실제 사례 (구체적 페르소나)
|
||||
|
||||
**필수 정보**:
|
||||
- 이름, 나이, 직업, 사업 경력
|
||||
- 월/연간 매출 (현실적 수치)
|
||||
- 실제 겪은 문제/성공 사례
|
||||
|
||||
```markdown
|
||||
### 상황: [지역] [카테고리]를 운영하는 [이름]님 ([나이]세, 사업 [년]차)
|
||||
|
||||
**기본 정보**:
|
||||
- 위치: [구체적 위치]
|
||||
- 월 매출: [금액]
|
||||
- 월 경비: [주요 항목들]
|
||||
|
||||
### 원래는 이렇게 했어요 (실패 사례)
|
||||
→ [실제 실수 1]
|
||||
→ [실제 실수 2]
|
||||
→ **결과**: 세금을 [X만 원] 더 내게 됨 (또는 세무조사 대상)
|
||||
|
||||
### 바뀐 후 (성공 사례)
|
||||
→ [해결책 1]
|
||||
→ [해결책 2]
|
||||
→ **결과**: 세금을 [X만 원] 절약함 (또는 안정적인 운영)
|
||||
```
|
||||
|
||||
**예시**:
|
||||
```markdown
|
||||
### 상황: 강남 역삼동에서 카페를 운영하는 김민수님 (34세, 사업 3년차)
|
||||
|
||||
**기본 정보**:
|
||||
- 위치: 강남역 3번 출구 근처
|
||||
- 월 매출: 약 600만 원 (평일 200만, 주말 400만)
|
||||
- 월 경비: 월세 150만, 재료비 180만, 직원급여 100만 원
|
||||
|
||||
### 원래는 이렇게 했어요
|
||||
→ "세금은 큰 회사나 내는 거라고 생각했어요"
|
||||
→ 영수증도 대충 정리하고
|
||||
→ **결과**: 세무청에서 3년치를 추징받고 가산세까지...손해 70만 원
|
||||
|
||||
### 바뀐 후
|
||||
→ 매달 영수증을 정리해서
|
||||
→ 세무사와 년 1회 기장 상담
|
||||
→ **결과**: 세금도 명확하고, 추징도 없음. 심플하고 안전
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Step 3: 계산 & 설명
|
||||
|
||||
**구조**:
|
||||
1. **기본 정보 확인** (위에서 제시한 사례 요약)
|
||||
2. **단계별 계산** (Step 1, Step 2, ... 명확히)
|
||||
3. **표로 시각화**
|
||||
|
||||
```markdown
|
||||
## 계산 방법
|
||||
|
||||
### Step 1️⃣: 매출 정리
|
||||
월 600만 원 × 12개월 = 연 7,200만 원
|
||||
|
||||
### Step 2️⃣: 경비 계산
|
||||
|
||||
월 경비 구성:
|
||||
- 월세: 150만 원 (연 1,800만 원)
|
||||
- 재료비: 180만 원 (연 2,160만 원)
|
||||
- 직원급여: 100만 원 (연 1,200만 원)
|
||||
- 기타: 20만 원 (연 240만 원)
|
||||
- **월 합계: 450만 원**
|
||||
- **연 합계: 5,400만 원**
|
||||
|
||||
### Step 3️⃣: 순이익
|
||||
7,200만 - 5,400만 = **1,800만 원**
|
||||
|
||||
### Step 4️⃣: 세금
|
||||
1,800만 원 × 약 6% = **약 108만 원/년**
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 🎭 Step 3.5: 악마는 디테일이다 (선택사항이지만 강력함)
|
||||
|
||||
**구조**: "간단해 보이지만, 실제로는..."
|
||||
|
||||
```markdown
|
||||
## 겉으로는 간단해 보여요... 하지만
|
||||
|
||||
### 📄 "영수증을 정리하세요"라고 했는데
|
||||
|
||||
**겉으로는**:
|
||||
→ 영수증을 모으기만 하면 돼
|
||||
|
||||
**현실의 디테일**:
|
||||
→ 이 영수증은 인정되고, 이건 안 됨 (세법)
|
||||
→ 이건 개인비? 사업비? (판단)
|
||||
→ 카드값이랑 현금값이랑 다르면? (대사)
|
||||
→ 3년 지났는데 영수증을 못 찾으면? (소송)
|
||||
→ 세무청이 불인정하면? (항의 절차)
|
||||
|
||||
**세무사가 처리하는 것**:
|
||||
✅ 어떤 영수증이 인정될지 사전에 판단
|
||||
✅ 개인비와 사업비의 경계 명확히
|
||||
✅ 세법 변경사항 적용
|
||||
✅ 세무청 부인시 대응 준비
|
||||
|
||||
---
|
||||
|
||||
### 📊 "매출과 경비를 기록하세요"라고 했는데
|
||||
|
||||
**겉으로는**:
|
||||
→ 엑셀에 숫자만 입력하면 돼
|
||||
|
||||
**현실의 디테일**:
|
||||
→ 카드 명세서와 입금액이 안 맞음 (환불? 수수료?)
|
||||
→ 한 달간 매출을 빼먹음 (추가 계산)
|
||||
→ 같은 카테고리인데 세법상 다르게 분류돼야 함 (부가세/소득세 다름)
|
||||
→ 작년에 잘못 입력한 게 발견됨 (수정신고)
|
||||
→ 월별로 편차가 커서 세무청이 의심함 (설명 준비)
|
||||
|
||||
**세무사가 처리하는 것**:
|
||||
✅ 카드명세서 vs 입금액 정산
|
||||
✅ 누락된 부분 찾아서 추가
|
||||
✅ 세법상 올바른 분류
|
||||
✅ 이전년도 오류 수정신고
|
||||
✅ 세무청 질의에 대한 근거 제시
|
||||
|
||||
---
|
||||
|
||||
### ✅ "정확하게 기장하면 세금이 확정돼요"라고 했는데
|
||||
|
||||
**겉으로는**:
|
||||
→ 기장만 잘하면 세금 끝
|
||||
|
||||
**현실의 디테일**:
|
||||
→ 같은 사업도 절세 방법이 다양함 (어떤 게 맞나?)
|
||||
→ 올해는 이렇게, 내년은 저렇게? (일관성)
|
||||
→ 부가세와 소득세 둘 다 고려해야 함 (연계 계산)
|
||||
→ 세무조사가 오면 3년치를 모두 봄 (소급 처리)
|
||||
→ 이의신청/항소하려면? (법적 절차)
|
||||
|
||||
**세무사가 처리하는 것**:
|
||||
✅ 최적의 절세 전략 제시
|
||||
✅ 연도별 일관된 기장 방식 유지
|
||||
✅ 부가세/소득세 동시 최적화
|
||||
✅ 세무조사 대비 사전 정리
|
||||
✅ 이의신청/항소 등 법적 대응
|
||||
```
|
||||
|
||||
**💡 핵심**:
|
||||
- 기초는 누구나 배울 수 있어요
|
||||
- **하지만 디테일을 모두 처리하려면?**
|
||||
- **그 디테일들이 바로 세무사가 하는 일**
|
||||
- **디테일 하나 놓쳤다가 가산세 50만 원... 이래서 세무사 비용이 아깝지 않음**
|
||||
|
||||
---
|
||||
|
||||
### 🔄 Step 3.6: 세법은 계속 바뀐다 (매년 업데이트)
|
||||
|
||||
**구조**: "올해의 세법 변화"를 포스트 작성 시점에 맞춰 갱신
|
||||
|
||||
```markdown
|
||||
## 그런데 세법은 해마다 바뀝니다
|
||||
|
||||
### 📋 [연도] 변경사항들 (꼭 알아야 할 것들)
|
||||
|
||||
**✅ 2025년 부가세 변화**:
|
||||
- 신고 기한이 [날짜]로 변경됨
|
||||
- 영세사업자 기준이 [금액]로 상향조정됨
|
||||
- 새로운 공제 항목이 추가됨: [항목들]
|
||||
|
||||
**✅ 2025년 소득세 변화**:
|
||||
- 기본공제가 [금액]에서 [금액]로 증가
|
||||
- 자녀 공제 조건이 변경됨
|
||||
- 월급 원천징수 기준이 조정됨
|
||||
|
||||
**✅ 2025년 새로운 제도**:
|
||||
- 소상공인 세금 감면 확대
|
||||
- 청년사업자 지원 강화
|
||||
- 부가가치세 간편신청 범위 확대
|
||||
|
||||
---
|
||||
|
||||
**혼자서 할 때의 문제**:
|
||||
❌ "작년 기준으로 기장했는데 올해 기준이 바뀐 거야?"
|
||||
❌ "이 공제가 되는 건지 안 되는 건지 모르겠어"
|
||||
❌ "새로운 제도가 나왔다는 것도 몰랐어"
|
||||
❌ "처음 다시 계산해야 하나?"
|
||||
|
||||
**세무사가 처리하는 것**:
|
||||
✅ 매년 변경사항 자동 추적
|
||||
✅ 당신의 상황에 맞는 새로운 공제 적용
|
||||
✅ 이전년도 재계산 필요시 수정신고
|
||||
✅ 연중 세법 개정 소식 안내
|
||||
✅ 새로운 지원 정책 놓치지 않게 관리
|
||||
|
||||
---
|
||||
|
||||
## 결과 비교: 혼자 할 때 vs 세무사와 함께
|
||||
|
||||
**세법 변화 추적**
|
||||
- 혼자: "어? 규칙이 바뀌었네?"
|
||||
- 세무사: 자동으로 적용됨
|
||||
|
||||
**새로운 공제**
|
||||
- 혼자: 놓치기 쉬움
|
||||
- 세무사: 모두 적용됨
|
||||
|
||||
**매년 재계산**
|
||||
- 혼자: 직접 해야 함
|
||||
- 세무사: 자동 갱신
|
||||
|
||||
**마음 편함**
|
||||
- 혼자: 불안감 ("맞나?")
|
||||
- 세무사: 확신 ("전문가가 관리")
|
||||
|
||||
**투자 시간**
|
||||
- 혼자: 당신의 시간
|
||||
- 세무사: 포함 (전문가 비용)
|
||||
|
||||
---
|
||||
|
||||
## 요약: 왜 세무사가 필요한가
|
||||
|
||||
**기초는 배울 수 있지만**:
|
||||
- 세법은 매년 바뀌고
|
||||
- 당신은 본업이 있어서 추적이 어렵고
|
||||
- 실수 하나가 가산세 50만 원...
|
||||
|
||||
**그래서 세무사가 있으면**:
|
||||
- 변화를 자동으로 적용해주고
|
||||
- 새 제도도 놓치지 않아주고
|
||||
- 당신은 사업에만 집중
|
||||
|
||||
→ **결국 시간, 돈, 스트레스 모두 절약**
|
||||
|
||||
---
|
||||
|
||||
### 💡 Step 4: 실무 팁 (3~5개)
|
||||
|
||||
**구조**: ✅ 이렇게 하세요 / ❌ 이렇게 하면 안 돼요
|
||||
|
||||
```markdown
|
||||
## 이렇게 하면 세금이 명확해요
|
||||
|
||||
### ✅ 해야 할 것
|
||||
1. **영수증 정리** - 매달 봉투에 모아두기
|
||||
2. **기본 기록** - 엑셀에 간단히 기입
|
||||
3. **연 1회 점검** - 세무사와 기본 상담
|
||||
4. **투명성** - 세무청 신고는 정확하게
|
||||
|
||||
### ❌ 하면 안 되는 것
|
||||
1. **영수증 버리기** - 나중에 증거 없음
|
||||
2. **개인비와 섞기** - 기장 혼란
|
||||
3. **신고 늦추기** - 가산세 발생
|
||||
4. **과하게 깎기** - 세무조사 리스크
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 📝 Step 5: 결론
|
||||
|
||||
고객이 읽은 후 자연스럽게 결론을 내리도록:
|
||||
|
||||
**구조**:
|
||||
1. 기초는 할 수 있다 (긍정)
|
||||
2. 근데 복잡하네요 (현실 직시)
|
||||
3. 그래서 세무사가 필요하구나 (자연스러운 깨달음)
|
||||
|
||||
**고객이 느끼는 여정**:
|
||||
- 처음: "아, 이 정도는 내가 할 수 있겠네"
|
||||
- 중간: "근데 이 모든 걸 매년 챙기기는..."
|
||||
- 결론: "전문가 도움이 낫겠다"
|
||||
|
||||
```markdown
|
||||
## 기초는 누구나 할 수 있어요
|
||||
|
||||
**이 정도면 자신이 충분히 가능합니다**:
|
||||
- 소규모 사업 (월 500만~1,000만 원)
|
||||
- 단순 경비 (재료, 임차료 등)
|
||||
- 월 1회 정도 기본 정리
|
||||
|
||||
→ 영수증 정리 + 기본 엑셀 기입면 충분
|
||||
|
||||
---
|
||||
|
||||
## 하지만 이렇게 복잡하면 전문가 도움이 효율적입니다
|
||||
|
||||
**세무사 상담을 권하는 경우**:
|
||||
- 📊 월 매출이 2,000만 원을 넘어갈 때
|
||||
- 💼 여러 사업을 동시에 운영할 때
|
||||
- 🏠 부동산 등 추가 수입이 있을 때
|
||||
- 📈 직원을 여러 명 두고 있을 때
|
||||
- 🌍 해외 거래나 수입이 있을 때
|
||||
|
||||
### 실제 효과: 숫자로 본 세무사의 가치
|
||||
|
||||
**절세액**
|
||||
- 혼자: X만 원
|
||||
- 세무사: X + 200만 원
|
||||
- 차이: +200만 원 절약
|
||||
|
||||
**세무조사 스트레스**
|
||||
- 혼자: 매년 불안
|
||||
- 세무사: 안정적 대응
|
||||
- 차이: 심리적 안정
|
||||
|
||||
**시간 투자**
|
||||
- 혼자: 월 10시간
|
||||
- 세무사: 월 1시간
|
||||
- 차이: 월 9시간 자유
|
||||
|
||||
**세무사 비용**
|
||||
- 혼자: 0원
|
||||
- 세무사: 약 100만 원/년
|
||||
- 차이: -100만 원
|
||||
|
||||
**실제 이익**
|
||||
- 혼자: 순이익
|
||||
- 세무사: 순이익 + 100만 원
|
||||
- 차이: +100만 원 순이익
|
||||
|
||||
**돈을 쓰는 이유**:
|
||||
- 세금 절약: 절세 200만 원 - 비용 100만 원 = 순 100만 원 이득
|
||||
- 시간 절약: 월 9시간(연 108시간) = 사업에 집중
|
||||
- 스트레스 감소: 세무조사 불안 제거
|
||||
- 리스크 관리: 실수로 인한 가산세 방지
|
||||
|
||||
---
|
||||
|
||||
## 요약
|
||||
|
||||
**기본 개념을 아는 것만으로도**:
|
||||
- 실수를 줄이고
|
||||
- 세금을 절약하고
|
||||
- 세무사와의 상담이 훨씬 효율적
|
||||
|
||||
당신의 상황이 어느 정도인지 판단하고,
|
||||
필요할 때 전문가와 함께 하세요.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ 작성 체크리스트
|
||||
|
||||
### 내용
|
||||
- [ ] **실제 사례**: 동네 카페/편의점/학원 같은 주변 상황
|
||||
- [ ] **구체적 페르소나**: 이름, 나이, 직업, 사업 경력
|
||||
- [ ] **실제 금액**: 매출, 경비, 세금 (현실적 수치)
|
||||
- [ ] **Before/After**: 실패 사례 → 성공 사례
|
||||
- [ ] **절세 효과**: "X만 원 절약" 또는 "손해를 막음"
|
||||
- [ ] **계산**: Step별로 명확, 표 포함
|
||||
- [ ] **악마는 디테일**: "겉으로는 간단해 보이지만 실제로는..." (세무사가 처리하는 디테일들)
|
||||
- [ ] **세법 변화**: 해당 연도의 세법 변경사항과 그 영향 설명
|
||||
|
||||
### 톤
|
||||
- [ ] **교육적**: 개념을 이해하도록
|
||||
- [ ] **격려적**: 경고/협박 없음
|
||||
- [ ] **현실적**: 복잡할 수 있다는 인정
|
||||
- [ ] **세무사 자연스러운 유도**: "필요할 때 도움되는 선택"
|
||||
- [ ] **임파워먼트**: "기초는 누구나 할 수 있어요"
|
||||
|
||||
### 표현
|
||||
- [ ] **중학교 수준**: 어려운 용어는 () 설명
|
||||
- [ ] **이모지**: 🏠💰✅❌📊 등으로 시각화
|
||||
- [ ] **짧은 문장**: 한 문장에 한 개념
|
||||
- [ ] **표와 리스트**: 수치는 표로, 항목은 리스트로
|
||||
|
||||
---
|
||||
|
||||
## 🚫 피해야 할 표현 (한국세무사협회 광고 규칙 준수)
|
||||
|
||||
### ❌ **절대 금지 표현** (법적 위반 위험)
|
||||
|
||||
**1. 과도한 절세 약속 & 절대 표현**:
|
||||
- ❌ "50만 원 절약 가능"
|
||||
- ❌ "최대한 경비를 깎아줍니다"
|
||||
- ❌ "세금을 반으로 줄여드립니다"
|
||||
- ❌ "세금을 덜 냅니다" (보장으로 해석)
|
||||
- ❌ "가장 많이 절세해드립니다"
|
||||
- ✅ "이 사례에서는 약 50만 원 절약되었습니다" (과거 사례만)
|
||||
- ✅ "정확한 경비 처리로 세법에 따른 정당한 공제를 받을 수 있습니다" (법적 근거)
|
||||
- ✅ "경비를 빠짐없이 처리합니다" (객관적 프로세스)
|
||||
|
||||
**2. 보장 표현 (불가능한 결과 약속)**:
|
||||
- ❌ "반드시 세금을 줄입니다"
|
||||
- ❌ "세무조사 안 받게 해드립니다"
|
||||
- ❌ "100% 절세를 보장합니다"
|
||||
- ❌ "세금을 보장합니다"
|
||||
- ✅ "정확한 신고로 세무조사 리스크를 최소화합니다"
|
||||
- ✅ "세법에 따른 정당한 공제를 받을 수 있습니다"
|
||||
|
||||
**3. 무료 & 가격 표현**:
|
||||
- ❌ "무료로 세금 절약해드립니다"
|
||||
- ❌ "최저가 신고료"
|
||||
- ❌ "가장 저렴한 가격"
|
||||
- ✅ "합리적인 비용으로 전문 서비스를 제공합니다"
|
||||
|
||||
**4. 절대/최상급 표현**:
|
||||
- ❌ "반드시", "무조건", "반듯이", "항상", "절대"
|
||||
- ❌ "최고", "최우수", "1등", "유일"
|
||||
- ❌ "모든", "완벽하게"
|
||||
- ✅ "일반적으로", "대부분의 경우", "보통"
|
||||
|
||||
**5. 과도한 단순화 표현**:
|
||||
- ❌ "매우 편합니다", "너무 쉽습니다"
|
||||
- ❌ "아무도 실수할 수 없습니다"
|
||||
- ❌ "5분이면 끝납니다"
|
||||
- ✅ "기초 개념을 배울 수 있습니다"
|
||||
- ✅ "복잡한 부분은 전문가가 관리합니다"
|
||||
|
||||
**6. 객관적 증거 없는 수치**:
|
||||
- ❌ "평균 170만 원 절약" (근거 없으면)
|
||||
- ❌ "고객의 80%가 만족" (통계 없으면)
|
||||
- ❌ "보통 2배의 환급" (데이터 없으면)
|
||||
- ✅ "이 사례에서는 약 170만 원 절약되었습니다"
|
||||
- ✅ "많은 고객들이 정확한 기장의 필요성을 느낍니다"
|
||||
|
||||
---
|
||||
|
||||
### ✅ **안전한 표현 (권장)**
|
||||
|
||||
| 대신 이렇게 | 이유 |
|
||||
|----------|------|
|
||||
| "정확한 기장으로 세법에 따른 공제를 받을 수 있습니다" | 법적 근거 (보장 아님) |
|
||||
| "경비를 빠짐없이 처리합니다" | 객관적 프로세스 |
|
||||
| "이 사례에서는 약 50만 원 절약되었습니다" | 과거 사례 (보장 아님) |
|
||||
| "경비를 빠짐없이 처리합니다" | 객관적 프로세스 |
|
||||
| "세무조사 대비 근거를 정리합니다" | 예방적 표현 |
|
||||
| "당신의 상황에 맞는 최선의 방법을 제시합니다" | 개별 맞춤형 |
|
||||
| "세법이 자주 바뀌므로 전문가 도움이 효율적입니다" | 필요성 설명 |
|
||||
| "이 정도는 자신이 충분히 가능합니다" | 존중과 임파워먼트 |
|
||||
| "복잡한 경우는 전문가와 상담하세요" | 선택지 제시 |
|
||||
| "정확하게 하면 나중에 편합니다" | 미래 가치 (현재 보장 아님) |
|
||||
|
||||
---
|
||||
|
||||
### 📋 블로그 작성 시 광고 규칙 체크리스트
|
||||
|
||||
- [ ] **절세 약속 제거**: "최대한", "반드시", "보장", "무조건" 단어 없음
|
||||
- [ ] **보장 표현 제거**: "세무조사 안 받게", "100% 절세", "확실" 제거
|
||||
- [ ] **무료/가격 표현 제거**: "무료", "최저가", "가장 저렴" 제거
|
||||
- [ ] **절대 표현 제거**: "항상", "절대", "모두", "완벽" 제거
|
||||
- [ ] **최상급 제거**: "최고", "최우수", "1등" (객관적 증거 있으면 가능)
|
||||
- [ ] **과도한 단순화 제거**: "매우 쉽습니다", "아무도 실수할 수 없음" 제거
|
||||
- [ ] **수치는 사례로**: "절약 가능" → "이 사례에서는 약 X만 원 절약"
|
||||
- [ ] **객관성 유지**: 구체적 사례 + 과거형 표현 사용
|
||||
- [ ] **필요성 설명**: "왜 필요한가" → 이해와 선택 유도
|
||||
- [ ] **세무사협회 규정 준수**: 법적 문제 없음
|
||||
|
||||
---
|
||||
|
||||
## 시즌별 주제 예시
|
||||
|
||||
| 월 | 추천 주제 | 톤 |
|
||||
|----|---------|-----|
|
||||
| 1월 | 부가세 2기 신고 기한 | "이 정도면 자신이 가능" |
|
||||
| 5월 | 종소세 신고 방법 | "핵심 개념 + 전문가 도움 타이밍" |
|
||||
| 7월 | 부가세 1기 신고 | "기초 정리 방법" |
|
||||
| 11월 | 다음해 준비 | "계획하면 편해요" |
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 실수 방지 체크리스트 (과거 오류 기록)
|
||||
|
||||
**이전에 반복된 실수들을 기록하여, 같은 실수를 하지 않도록 합니다.**
|
||||
|
||||
### 1️⃣ 카테고리 할당 실수 ❌
|
||||
|
||||
**과거 오류**: 포스트를 만들 때 category_id를 NULL로 두었음
|
||||
|
||||
**문제점**:
|
||||
- DB NOT NULL 제약 위반
|
||||
- 블로그 페이지에 노출 안 됨
|
||||
- 고객이 카테고리로 검색 불가
|
||||
|
||||
**예방책**:
|
||||
- ✅ **SQL INSERT 시 반드시 category_id 명시**
|
||||
- ✅ **포스트 작성 전에 카테고리 결정**
|
||||
- ✅ **DB 적용 후 category_id NOT NULL 확인**
|
||||
- ✅ **각 카테고리별 최소 3개 이상 포스트 유지**
|
||||
|
||||
**SQL 예시** (권장):
|
||||
```sql
|
||||
INSERT INTO blog_posts (title, slug, content, category_id, is_published, ...)
|
||||
VALUES ('제목', 'slug', $$본문$$, 1, true, ...);
|
||||
-- category_id 절대 생략 금지!
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2️⃣ 내용 길이 부족 ❌
|
||||
|
||||
**과거 오류**: 에이전트가 지침(1,500~2,500자)을 무시하고 간단한 버전(500자)으로 생성
|
||||
|
||||
**문제점**:
|
||||
- 고객 설득력 부족
|
||||
- 계산 예시 없음
|
||||
- 3단계 구조 불완전
|
||||
- 세법 인용 부족
|
||||
|
||||
**예방책**:
|
||||
- ✅ **각 포스트 최소 1,500자 이상 (추천 2,000~2,500자)**
|
||||
- ✅ **포스트 작성 후 글자 수 확인: `LENGTH(content) >= 1500`**
|
||||
- ✅ **항상 실제 사례 포함** (이름, 나이, 직업, 구체적 상황)
|
||||
- ✅ **항상 계산 과정 포함** (절세액 수치화)
|
||||
- ✅ **3단계 구조 필수** (1️⃣ 기초 → 2️⃣ 현실 → 3️⃣ 해결책)
|
||||
|
||||
**확인 쿼리**:
|
||||
```sql
|
||||
SELECT id, title, LENGTH(content) as length FROM blog_posts
|
||||
WHERE LENGTH(content) < 1500; -- 부족한 포스트 검출
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3️⃣ 테이블 사용 금지 ❌
|
||||
|
||||
**과거 오류**: 마크다운 테이블(`| |---|---|`) 사용
|
||||
|
||||
**문제점**:
|
||||
- 지침 위반 (리스트만 사용)
|
||||
- 모바일에서 가독성 저하
|
||||
- 유지보수 어려움
|
||||
|
||||
**예방책**:
|
||||
- ✅ **테이블 금지, 리스트만 사용** (- 또는 숫자 목록)
|
||||
- ✅ **작성 후 `| |` 패턴 검색으로 테이블 확인**
|
||||
- ✅ **수치/계산은 리스트 형식**:
|
||||
|
||||
**❌ 금지 (테이블)**:
|
||||
```markdown
|
||||
| 항목 | 월 | 연간 |
|
||||
|------|-----|------|
|
||||
| 월세 | 150만 | 1,800만 |
|
||||
```
|
||||
|
||||
**✅ 권장 (리스트)**:
|
||||
```markdown
|
||||
월 경비 구성:
|
||||
- 월세: 150만 원 (연 1,800만 원)
|
||||
- 재료비: 180만 원 (연 2,160만 원)
|
||||
- 직원급여: 100만 원 (연 1,200만 원)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4️⃣ 계산 예시 누락 ❌
|
||||
|
||||
**과거 오류**: 포스트에 개념만 있고 실제 계산 예시 부족
|
||||
|
||||
**문제점**:
|
||||
- 고객이 "내 상황에 얼마나 해당하나" 판단 어려움
|
||||
- 추상적 설명으로 설득력 감소
|
||||
- 세무사 필요성 전달 미흡
|
||||
|
||||
**예방책**:
|
||||
- ✅ **모든 포스트에 구체적 계산 예시 필수**
|
||||
- ✅ **절세액을 수치로 제시** ("약 50만 원 절약")
|
||||
- ✅ **단계별 계산 과정 포함** (Step 1️⃣, 2️⃣, 3️⃣, 4️⃣)
|
||||
- ✅ **실제 사례로 숫자 구체화**:
|
||||
|
||||
**예시**:
|
||||
```markdown
|
||||
### Step 1️⃣: 매출 정리
|
||||
월 600만 원 × 12개월 = 연 7,200만 원
|
||||
|
||||
### Step 2️⃣: 경비 계산
|
||||
- 월세: 150만 원 → 연 1,800만 원
|
||||
- 재료비: 180만 원 → 연 2,160만 원
|
||||
합계: 5,400만 원
|
||||
|
||||
### Step 3️⃣: 순이익
|
||||
7,200만 - 5,400만 = 1,800만 원
|
||||
|
||||
### Step 4️⃣: 세금
|
||||
1,800만 원 × 약 6% = **약 108만 원/년**
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5️⃣ 카테고리 주제 불일치 ❌
|
||||
|
||||
**과거 오류**: 포스트 주제와 카테고리가 맞지 않음
|
||||
|
||||
**문제점**:
|
||||
- 고객이 원하는 정보 검색 불가
|
||||
- 카테고리 신뢰도 저하
|
||||
- UX 혼란
|
||||
|
||||
**예방책**:
|
||||
- ✅ **포스트 작성 전 카테고리 명확히 결정**
|
||||
- ✅ **포스트 주제와 카테고리 일관성 검증**:
|
||||
|
||||
| 포스트 | 카테고리 | 확인 |
|
||||
|--------|---------|------|
|
||||
| 프리랜서 경비 | 종합소득세 (3) | ✅ 맞음 |
|
||||
| 월세 신고 | 부동산 세금 (2) | ✅ 맞음 |
|
||||
| 자녀 증여세 | 가족자산·증여 (5) | ✅ 맞음 |
|
||||
| 사업자 기장 | 사업자 세무 (1) | ✅ 맞음 |
|
||||
| 부가세 신고 | 부가가치세 (4) | ✅ 맞음 |
|
||||
|
||||
---
|
||||
|
||||
### 6️⃣ 정확한 세법 인용 누락 ❌
|
||||
|
||||
**과거 오류**: 일부 포스트에서 법조 명시 부족
|
||||
|
||||
**문제점**:
|
||||
- 정확성 원칙 위반
|
||||
- 법적 책임 불명확
|
||||
- 고객 신뢰도 저하
|
||||
|
||||
**예방책**:
|
||||
- ✅ **모든 주요 내용에 세법 조항 인용 필수**
|
||||
- ✅ **형식**: "소득세법 제XX조에 따르면"
|
||||
- ✅ **연도 기준 명시**: "2025년 기준"
|
||||
- ✅ **포스트 끝에 "법적 근거" 섹션 필수**:
|
||||
|
||||
```markdown
|
||||
**법적 근거**:
|
||||
- 소득세법 제29조 (수입금액의 계산)
|
||||
- 국세기본법 제47조 (가산세)
|
||||
- 소득세법 제160조 (증빙 보관)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ 포스트 최종 체크리스트
|
||||
|
||||
모든 포스트를 DB에 등록하기 전에 다음을 확인하세요:
|
||||
|
||||
- [ ] **카테고리 할당**: `category_id NOT NULL` (필수)
|
||||
- [ ] **내용 길이**: `LENGTH(content) >= 1500` (최소 1,500자)
|
||||
- [ ] **테이블 확인**: `| |` 패턴 없음 (리스트만)
|
||||
- [ ] **계산 예시**: Step 1️⃣~4️⃣ 포함 (절세액 수치)
|
||||
- [ ] **세법 인용**: 모든 주요 내용에 법조 명시
|
||||
- [ ] **카테고리 일치**: 포스트 주제 ↔ 카테고리 일관성
|
||||
- [ ] **3단계 구조**: 1️⃣ 기초 → 2️⃣ 현실 → 3️⃣ 해결책
|
||||
- [ ] **광고 규칙**: 금지 표현(보장, 최저가, 무료) 없음
|
||||
- [ ] **사례 포함**: 실제 상황 + 이름/나이/직업 구체화
|
||||
- [ ] **정확성**: 추측/예상/의견 표현 없음
|
||||
|
||||
**체크 쿼리**:
|
||||
```sql
|
||||
-- DB 적용 후 확인
|
||||
SELECT id, title, LENGTH(content), category_id
|
||||
FROM blog_posts
|
||||
WHERE LENGTH(content) < 1500 OR category_id IS NULL
|
||||
ORDER BY id;
|
||||
-- 결과 없음이 정상!
|
||||
```
|
||||
@@ -0,0 +1,17 @@
|
||||
# CI/CD 자동 배포 테스트
|
||||
|
||||
**테스트 시간**: 2026-06-26 15:40 KST
|
||||
**브랜치**: master
|
||||
**목표**: Gitea Actions 자동 배포 검증
|
||||
|
||||
## 배포 파이프라인
|
||||
1. ✅ 코드 푸시
|
||||
2. ⏳ Gitea Actions 트리거
|
||||
3. ⏳ 빌드 실행
|
||||
4. ⏳ 발행 실행
|
||||
5. ⏳ 서버 배포
|
||||
6. ⏳ 서비스 재시작
|
||||
|
||||
## 모니터링
|
||||
- Gitea Actions 탭 확인
|
||||
- 약 3분 후 배포 완료
|
||||
@@ -0,0 +1,620 @@
|
||||
# 클라우드 서버 설정 가이드 (hz-prod-01)
|
||||
|
||||
> 시놀로지(Synology DSM)에서 클라우드 VPS(`178.104.200.7`)로 이전.
|
||||
> 이 문서는 서버에서 실제 수집된 데이터 기반이며, 운영 하네스로 사용한다.
|
||||
|
||||
---
|
||||
|
||||
## 참조 인덱스
|
||||
|
||||
| # | 섹션 | 핵심 내용 |
|
||||
|---|---|---|
|
||||
| 1 | [서버 기본 정보](#1-서버-기본-정보) | 호스트명, IP, OS, CPU/RAM/디스크, 타임존 |
|
||||
| 2 | [접속 정보](#2-접속-정보) | SSH 접속, 사용자, 인증 방식 |
|
||||
| 3 | [소프트웨어 스택](#3-소프트웨어-스택) | Python, .NET, PG, Nginx, Docker Compose, fail2ban |
|
||||
| 3.1 | [런타임](#31-런타임) | 버전/경로 일람 |
|
||||
| 3.2 | [Python 가상 환경](#32-python-가상-환경) | `~/.venv`, `python3` 사용 규칙 |
|
||||
| 3.3 | [주요 Python 패키지](#33-주요-python-패키지-시스템) | 시스템/venv 패키지 구분 |
|
||||
| 4 | [서비스 아키텍처](#4-서비스-아키텍처) | 포트 맵, Nginx 리버스 프록시 |
|
||||
| 4.1 | [포트 맵](#41-포트-맵) | 22, 80, 2222, 3000, 5000, 5432 |
|
||||
| 4.2 | [Nginx 리버스 프록시](#42-nginx-리버스-프록시) | 도메인 기반 가상 호스트 분기 (홈페이지, Gitea, Quant) |
|
||||
| 5 | [Gitea](#5-gitea) | Docker Compose 설정, 시크릿, 데이터 경로 |
|
||||
| 5.1 | [Docker Compose](#51-docker-compose) | `gitea:1.26.4`, PG 연동 |
|
||||
| 5.2 | [시크릿 관리](#52-시크릿-관리) | `/opt/stacks/gitea/.env` |
|
||||
| 5.3 | [데이터](#53-데이터) | Gitea 볼륨, `giteadb` |
|
||||
| 6 | [Gitea Act Runner (CI)](#6-gitea-act-runner-ci) | 6× 러너, 네트워크, 구성 디렉토리 |
|
||||
| 6.1 | [컨테이너 현황](#61-컨테이너-현황) | 러너 6개 실행 상태 |
|
||||
| 6.2 | [러너 설정](#62-러너-설정) | `hz-prod-runner`, `gitea_default` 네트워크 |
|
||||
| 6.3 | [러너 구성 디렉토리](#63-러너-구성-디렉토리) | `~/gitea-runner[-N]/` |
|
||||
| 7 | [QuantEngine Blazor Admin](#7-quantengine-blazor-admin) | systemd, symlink 배포, DLL 구성 |
|
||||
| 7.1 | [systemd 서비스](#71-systemd-서비스) | `quantengine.service` 전문 |
|
||||
| 7.2 | [배포 구조](#72-배포-구조) | 타임스탬프 디렉토리 + symlink 교체 |
|
||||
| 7.3 | [주요 DLL](#73-주요-dll) | Web, Core, Infrastructure, MudBlazor, Dapper |
|
||||
| 8 | [PostgreSQL 18](#8-postgresql-18) | v18.4, `localhost` 바인드, Docker 연동 |
|
||||
| 9 | [보안](#9-보안) | SSH hardening, UFW, fail2ban, 네트워크 격리 |
|
||||
| 9.1 | [SSH 보안 설정](#91-ssh-보안-설정) | 공개키 전용, root 차단 |
|
||||
| 9.2 | [UFW 방화벽](#92-ufw-방화벽) | `ENABLED=yes`, 포트 개방/차단 |
|
||||
| 9.3 | [fail2ban](#93-fail2ban) | SSH 브루트포스 방어 |
|
||||
| 9.4 | [Docker 네트워크 격리](#94-docker-네트워크-격리) | 로컬바인드 정책 |
|
||||
| 10 | [디렉토리 맵](#10-디렉토리-맵) | `/home/kjh2064/`, `/opt/stacks/`, `/opt/backups/` |
|
||||
| 11 | [시놀로지 → 클라우드 마이그레이션 매핑](#11-시놀로지--클라우드-마이그레이션-매핑) | 항목별 구↔신 비교표 |
|
||||
| 12 | [운영 명령 치트시트](#12-운영-명령-치트시트) | 서비스 관리, 배포, 러너 등록, SSH |
|
||||
| 13 | [검증 하네스](#13-검증-하네스) | 헬스체크, 엔드포인트, 마이그레이션 체크리스트 |
|
||||
|
||||
### 관련 문서 상호 참조
|
||||
|
||||
| 문서 | 역할 |
|
||||
|---|---|
|
||||
| [`AGENTS.md`](../AGENTS.md) | 운영 헌법, Directory Routing 인덱스 |
|
||||
| [`GITEA_SECRETS_SETUP.md`](GITEA_SECRETS_SETUP.md) | Gitea 시크릿 설정/검증 가이드 |
|
||||
| [`ROADMAP_WBS.md`](ROADMAP_WBS.md) | `.gs → Python` 및 `xlsx → sqlite` WBS |
|
||||
| [`docs/GITEA_TOKEN_HOME_RUNBOOK.md`](GITEA_TOKEN_HOME_RUNBOOK.md) | Gitea 토큰 관리 런북 |
|
||||
| [`spec/00_execution_contract.yaml`](../spec/00_execution_contract.yaml) | 실행 계약 원본 권위 |
|
||||
| [`governance/agents_index.yaml`](../governance/agents_index.yaml) | 거버넌스 규칙 인덱스 |
|
||||
|
||||
---
|
||||
|
||||
## 1. 서버 기본 정보
|
||||
|
||||
| 항목 | 값 |
|
||||
|---|---|
|
||||
| **호스트명** | `hz-prod-01` |
|
||||
| **IP** | `178.104.200.7` |
|
||||
| **OS** | Ubuntu 26.04 LTS (Resolute Raccoon) |
|
||||
| **커널** | `7.0.0-22-generic` (x86_64, PREEMPT_DYNAMIC) |
|
||||
| **CPU** | AMD EPYC-Rome, 2 vCPU |
|
||||
| **메모리** | 3.7 GiB (사용 ~958 MiB, 가용 ~2.8 GiB) |
|
||||
| **스왑** | 2.0 GiB |
|
||||
| **디스크** | `/dev/sda1` 38 GB (사용 8.5 GB / 28 GB 가용, 24%) |
|
||||
| **타임존** | `Asia/Seoul` (KST, +0900), NTP 동기화 활성 |
|
||||
|
||||
## 2. 접속 정보
|
||||
|
||||
| 항목 | 값 |
|
||||
|---|---|
|
||||
| **SSH 접속** | `ssh kjh2064@178.104.200.7` |
|
||||
| **SSH 포트** | 22 (기본) |
|
||||
| **사용자** | `kjh2064` (uid=1000) |
|
||||
| **그룹** | `kjh2064`, `sudo`, `users`, `docker` |
|
||||
| **인증 방식** | 공개키 전용 (`PasswordAuthentication no`) |
|
||||
| **Root 로그인** | 비활성 (`PermitRootLogin no`) |
|
||||
| **Max Auth Tries** | 3 |
|
||||
| **Keep-Alive** | `ClientAliveInterval 300`, `ClientAliveCountMax 2` |
|
||||
|
||||
## 3. 소프트웨어 스택
|
||||
|
||||
### 3.1. 런타임
|
||||
|
||||
| 소프트웨어 | 버전 | 경로 |
|
||||
|---|---|---|
|
||||
| **Python** | 3.14.4 | `/usr/bin/python3` |
|
||||
| **.NET SDK** | 10.0.109 | `/usr/lib/dotnet/sdk` |
|
||||
| **.NET Runtime** | ASP.NET Core 10.0.9 + NETCore 10.0.9 | `/usr/lib/dotnet/shared/` |
|
||||
| **PostgreSQL** | 18.4 | `postgresql@18-main.service` |
|
||||
| **Nginx** | 시스템 패키지 | `nginx.service` |
|
||||
| **Docker Compose** | v5.2.0 | Docker 플러그인 |
|
||||
| **fail2ban** | 1.1.0 | `fail2ban.service` |
|
||||
|
||||
### 3.2. Python 가상 환경
|
||||
|
||||
```
|
||||
경로: ~/.venv
|
||||
Python: 3.14.4
|
||||
```
|
||||
|
||||
> **주의**: 이 서버에서는 `python3`을 사용한다 (시놀로지/Windows와 다름).
|
||||
> CI 워크플로우와 로컬 서버 모두 `python3`을 사용하므로 통일됨.
|
||||
|
||||
### 3.3. 주요 Python 패키지 (시스템)
|
||||
|
||||
boto3, cryptography, Jinja2, jsonschema, fail2ban 등 시스템 레벨로 설치됨.
|
||||
프로젝트 의존성은 `~/.venv`에 별도 관리.
|
||||
|
||||
## 4. 서비스 아키텍처
|
||||
|
||||
### 4.1. 포트 맵
|
||||
|
||||
| 포트 | 서비스 | 바인드 | 비고 |
|
||||
|---|---|---|---|
|
||||
| **22** | SSH | `0.0.0.0` | 공개키 전용 |
|
||||
| **80** | Nginx (리버스 프록시) | `0.0.0.0` | 외부 진입점 |
|
||||
| **2222** | Gitea SSH | `0.0.0.0` | Git SSH 접속 |
|
||||
| **3000** | Gitea Web | `127.0.0.1` | Nginx 프록시 경유 |
|
||||
| **5000** | QuantEngine Blazor | `127.0.0.1` | Nginx `/quant/` 경유 |
|
||||
| **5432** | PostgreSQL | `127.0.0.1` + `172.17.0.1` | 로컬 + Docker 네트워크 |
|
||||
|
||||
### 4.2. Nginx 리버스 프록시
|
||||
|
||||
```nginx
|
||||
# /etc/nginx/sites-available/taxbaik-domains.conf
|
||||
|
||||
# 1. TaxBaik 홈페이지 (taxbaik.com, www.taxbaik.com)
|
||||
server {
|
||||
server_name taxbaik.com www.taxbaik.com;
|
||||
client_max_body_size 512M;
|
||||
|
||||
|
||||
# /admin 하위 요청을 /taxbaik/admin 으로 리다이렉트하여 Blazor Base Path 대응
|
||||
location /admin {
|
||||
return 301 $scheme://$host/taxbaik$request_uri;
|
||||
}
|
||||
|
||||
# 루트 경로 요청을 /taxbaik 으로 프록싱하여 base href /taxbaik/ 에 대응
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:5001/taxbaik/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "Upgrade";
|
||||
proxy_set_header Host $host;
|
||||
proxy_cache_bypass $http_upgrade;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
# /taxbaik/ 하위로 들어오는 리소스 및 페이지 요청 처리
|
||||
location /taxbaik {
|
||||
proxy_pass http://127.0.0.1:5001;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "Upgrade";
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_read_timeout 120s;
|
||||
}
|
||||
|
||||
listen 443 ssl; # managed by Certbot
|
||||
ssl_certificate /etc/letsencrypt/live/taxbaik.com/fullchain.pem; # managed by Certbot
|
||||
ssl_certificate_key /etc/letsencrypt/live/taxbaik.com/privkey.pem; # managed by Certbot
|
||||
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
|
||||
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
|
||||
|
||||
|
||||
}
|
||||
|
||||
# 2. Gitea (gitea.taxbaik.com)
|
||||
server {
|
||||
server_name gitea.taxbaik.com;
|
||||
client_max_body_size 512M;
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:3000;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_read_timeout 300;
|
||||
proxy_connect_timeout 300;
|
||||
proxy_send_timeout 300;
|
||||
}
|
||||
|
||||
listen 443 ssl; # managed by Certbot
|
||||
ssl_certificate /etc/letsencrypt/live/taxbaik.com/fullchain.pem; # managed by Certbot
|
||||
ssl_certificate_key /etc/letsencrypt/live/taxbaik.com/privkey.pem; # managed by Certbot
|
||||
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
|
||||
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
|
||||
|
||||
}
|
||||
|
||||
# 3. QuantEngine (quant.taxbaik.com)
|
||||
server {
|
||||
server_name quant.taxbaik.com;
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:5000/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "Upgrade";
|
||||
proxy_set_header Host $host;
|
||||
proxy_cache_bypass $http_upgrade;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
listen 443 ssl; # managed by Certbot
|
||||
ssl_certificate /etc/letsencrypt/live/taxbaik.com/fullchain.pem; # managed by Certbot
|
||||
ssl_certificate_key /etc/letsencrypt/live/taxbaik.com/privkey.pem; # managed by Certbot
|
||||
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
|
||||
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
|
||||
|
||||
}
|
||||
|
||||
server {
|
||||
if ($host = www.taxbaik.com) {
|
||||
return 301 https://$host$request_uri;
|
||||
} # managed by Certbot
|
||||
|
||||
|
||||
if ($host = taxbaik.com) {
|
||||
return 301 https://$host$request_uri;
|
||||
} # managed by Certbot
|
||||
|
||||
|
||||
listen 80;
|
||||
server_name taxbaik.com www.taxbaik.com;
|
||||
return 404; # managed by Certbot
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
server {
|
||||
if ($host = gitea.taxbaik.com) {
|
||||
return 301 https://$host$request_uri;
|
||||
} # managed by Certbot
|
||||
|
||||
|
||||
listen 80;
|
||||
server_name gitea.taxbaik.com;
|
||||
return 404; # managed by Certbot
|
||||
|
||||
|
||||
}
|
||||
server {
|
||||
if ($host = quant.taxbaik.com) {
|
||||
return 301 https://$host$request_uri;
|
||||
} # managed by Certbot
|
||||
|
||||
|
||||
listen 80;
|
||||
server_name quant.taxbaik.com;
|
||||
return 404; # managed by Certbot
|
||||
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
**라우팅 요약**:
|
||||
- `http://taxbaik.com/` 또는 `http://www.taxbaik.com/` → TaxBaik 홈페이지 (내부 proxy: `http://127.0.0.1:5001/taxbaik/`)
|
||||
- `http://gitea.taxbaik.com/` → Gitea Web UI (내부 proxy: `http://127.0.0.1:3000`)
|
||||
- `http://quant.taxbaik.com/` → QuantEngine Blazor Admin (내부 proxy: `http://127.0.0.1:5000/`)
|
||||
- `ssh://gitea.taxbaik.com:2222` → Gitea Git SSH
|
||||
|
||||
## 5. Gitea
|
||||
|
||||
### 5.1. Docker Compose
|
||||
|
||||
```yaml
|
||||
# /opt/stacks/gitea/docker-compose.yml
|
||||
services:
|
||||
gitea:
|
||||
image: docker.gitea.com/gitea:1.26.4
|
||||
container_name: gitea
|
||||
restart: unless-stopped
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
environment:
|
||||
USER_UID: "1000"
|
||||
USER_GID: "1000"
|
||||
GITEA__database__DB_TYPE: postgres
|
||||
GITEA__database__HOST: host.docker.internal:5432
|
||||
GITEA__database__NAME: giteadb
|
||||
GITEA__database__USER: gitea
|
||||
GITEA__database__PASSWD: "${GITEA_DB_PASSWORD}"
|
||||
GITEA__server__DOMAIN: "${SERVER_IP}"
|
||||
GITEA__server__ROOT_URL: "http://${SERVER_IP}/"
|
||||
GITEA__server__SSH_DOMAIN: "${SERVER_IP}"
|
||||
GITEA__server__SSH_PORT: "2222"
|
||||
GITEA__security__INSTALL_LOCK: "true"
|
||||
GITEA__service__DISABLE_REGISTRATION: "true"
|
||||
volumes:
|
||||
- ./gitea:/data
|
||||
- /etc/timezone:/etc/timezone:ro
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
ports:
|
||||
- "127.0.0.1:3000:3000"
|
||||
- "2222:22"
|
||||
```
|
||||
|
||||
### 5.2. 시크릿 관리
|
||||
|
||||
- `.env` 파일: `/opt/stacks/gitea/.env` (소유자 전용, `600`)
|
||||
- 포함 변수: `GITEA_DB_PASSWORD`, `SERVER_IP`
|
||||
|
||||
### 5.3. 데이터
|
||||
|
||||
- Gitea 데이터: `/opt/stacks/gitea/gitea/`
|
||||
- DB: PostgreSQL `giteadb` (Docker → host.docker.internal:5432 경유)
|
||||
|
||||
## 6. Gitea Act Runner (CI)
|
||||
|
||||
### 6.1. 컨테이너 현황
|
||||
|
||||
| 이름 | 이미지 | 상태 |
|
||||
|---|---|---|
|
||||
| `gitea-runner` | `gitea/act_runner:latest` | 실행 중 |
|
||||
| `gitea-runner-2` | `gitea/act_runner:latest` | 실행 중 |
|
||||
| `gitea-runner-3` | `gitea/act_runner:latest` | 실행 중 |
|
||||
| `hopeful_galileo` | `gitea/act_runner:latest` | 실행 중 |
|
||||
| `jovial_bouman` | `gitea/act_runner:latest` | 실행 중 |
|
||||
| `upbeat_chatelet` | `gitea/act_runner:latest` | 실행 중 |
|
||||
|
||||
> 총 6개 러너가 활성 상태. 네트워크는 `gitea_default` Docker 네트워크 사용.
|
||||
|
||||
### 6.2. 러너 설정
|
||||
|
||||
```yaml
|
||||
# ~/gitea-runner/config.yaml
|
||||
container:
|
||||
network: "gitea_default"
|
||||
```
|
||||
|
||||
- 러너 이름: `hz-prod-runner`
|
||||
- 러너 UUID: `d6d9120b-5070-4874-88d7-b86fe817d5a0`
|
||||
- 러너 이미지: `docker.gitea.com/runner-images:ubuntu-latest` (2.33 GB)
|
||||
|
||||
### 6.3. 러너 구성 디렉토리
|
||||
|
||||
```
|
||||
~/gitea-runner/ # 1번 러너
|
||||
~/gitea-runner-2/ # 2번 러너
|
||||
~/gitea-runner-3/ # 3번 러너
|
||||
```
|
||||
|
||||
## 7. QuantEngine Blazor Admin
|
||||
|
||||
### 7.1. systemd 서비스
|
||||
|
||||
```ini
|
||||
# /etc/systemd/system/quantengine.service
|
||||
[Unit]
|
||||
Description=Quant Engine Blazor Admin Web App (.NET 10)
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
WorkingDirectory=/home/kjh2064/quantengine_active
|
||||
ExecStart=/usr/bin/dotnet /home/kjh2064/quantengine_active/QuantEngine.Web.dll
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
KillSignal=SIGINT
|
||||
SyslogIdentifier=quantengine
|
||||
User=kjh2064
|
||||
Environment=ASPNETCORE_ENVIRONMENT=Production
|
||||
Environment=ASPNETCORE_URLS=http://127.0.0.1:5000
|
||||
Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
### 7.2. 배포 구조
|
||||
|
||||
```
|
||||
~/quantengine_active → ~/deployments/quantengine_20260625_182821 (symlink)
|
||||
~/deployments/
|
||||
├── quantengine_20260625_155649/
|
||||
├── quantengine_20260625_164548/
|
||||
├── quantengine_20260625_164928/
|
||||
└── quantengine_20260625_182821/ ← 현재 활성
|
||||
```
|
||||
|
||||
**배포 방식**: 타임스탬프 디렉토리 생성 → symlink 교체 → `systemctl restart quantengine`
|
||||
|
||||
### 7.3. 주요 DLL
|
||||
|
||||
- `QuantEngine.Web.dll` — 웹 진입점
|
||||
- `QuantEngine.Core.dll` — 핵심 도메인
|
||||
- `QuantEngine.Application.dll` — 애플리케이션 서비스
|
||||
- `QuantEngine.Infrastructure.dll` — 인프라 (DB, 외부 연동)
|
||||
- `Npgsql.dll` — PostgreSQL 드라이버
|
||||
- `MudBlazor.dll` — UI 컴포넌트
|
||||
- `Dapper.dll` — 마이크로 ORM
|
||||
|
||||
## 8. PostgreSQL 18
|
||||
|
||||
| 항목 | 값 |
|
||||
|---|---|
|
||||
| **버전** | 18.4 (Ubuntu 패키지) |
|
||||
| **서비스** | `postgresql@18-main.service` |
|
||||
| **listen_addresses** | `localhost` (기본값, 로컬 전용) |
|
||||
| **바인드** | `127.0.0.1:5432`, `172.17.0.1:5432` (Docker), `[::1]:5432` |
|
||||
| **Gitea DB** | `giteadb` (사용자: `gitea`) |
|
||||
|
||||
> Docker 컨테이너는 `host.docker.internal:5432`로 호스트 PG에 접속.
|
||||
> `listen_addresses`는 `postgresql.conf`에서 기본값 `localhost`로 설정됨 (외부 접속 차단).
|
||||
|
||||
## 9. 보안
|
||||
|
||||
### 9.1. SSH 보안 설정
|
||||
|
||||
```
|
||||
PermitRootLogin no
|
||||
PasswordAuthentication no
|
||||
PubkeyAuthentication yes
|
||||
KbdInteractiveAuthentication no
|
||||
X11Forwarding no
|
||||
MaxAuthTries 3
|
||||
ClientAliveInterval 300
|
||||
ClientAliveCountMax 2
|
||||
```
|
||||
|
||||
### 9.2. UFW 방화벽
|
||||
|
||||
- **상태**: `ENABLED=yes` (`/etc/ufw/ufw.conf`)
|
||||
- **로그 레벨**: `low`
|
||||
- **외부 개방 포트**: 22 (SSH), 80 (HTTP/Nginx), 2222 (Gitea SSH)
|
||||
- **내부 전용**: 3000 (Gitea Web), 5000 (QuantEngine), 5432 (PostgreSQL)
|
||||
|
||||
> 상세 규칙 확인: `sudo ufw status numbered` (TTY + sudo 비밀번호 필요)
|
||||
|
||||
### 9.3. fail2ban
|
||||
|
||||
- `fail2ban.service` 활성 상태
|
||||
- SSH 브루트포스 방어 활성
|
||||
|
||||
### 9.4. Docker 네트워크 격리
|
||||
|
||||
- Gitea Web: `127.0.0.1:3000` (로컬 전용)
|
||||
- QuantEngine: `127.0.0.1:5000` (로컬 전용)
|
||||
- PostgreSQL: `127.0.0.1` + Docker bridge (`172.17.0.1`)
|
||||
- 외부 노출: SSH(22), HTTP(80), Gitea SSH(2222)만 개방
|
||||
|
||||
## 10. 디렉토리 맵
|
||||
|
||||
```
|
||||
/home/kjh2064/
|
||||
├── quantengine_active → deployments/quantengine_YYYYMMDD_HHMMSS (symlink)
|
||||
├── deployments/ # QuantEngine 배포 히스토리
|
||||
│ └── quantengine_YYYYMMDD_HHMMSS/
|
||||
│ └── wwwroot/
|
||||
├── gitea-runner/ # Gitea Act Runner 1
|
||||
├── gitea-runner-2/ # Gitea Act Runner 2
|
||||
├── gitea-runner-3/ # Gitea Act Runner 3
|
||||
├── apps/ # 추가 앱
|
||||
│ └── python-test/.venv/
|
||||
├── .venv/ # Python 3.14 가상 환경
|
||||
├── tmp/ # 임시 작업
|
||||
└── .ssh/ # SSH 키
|
||||
|
||||
/opt/stacks/
|
||||
├── gitea/
|
||||
│ ├── docker-compose.yml
|
||||
│ ├── .env # GITEA_DB_PASSWORD, SERVER_IP
|
||||
│ └── gitea/ # Gitea 데이터 볼륨
|
||||
└── dotnet-app/ # .NET 관련
|
||||
|
||||
/opt/backups/ # 백업
|
||||
```
|
||||
|
||||
## 11. 시놀로지 → 클라우드 마이그레이션 매핑
|
||||
|
||||
| 항목 | 시놀로지 (구) | 클라우드 (신) |
|
||||
|---|---|---|
|
||||
| **프로젝트 경로** | `/volume1/projects/data_feed` | 미배치 (TBD) |
|
||||
| **Python** | `python3` (시스템) | `python3` (`/usr/bin/python3`, 3.14.4) |
|
||||
| **Gitea** | Docker on DSM | Docker on Ubuntu (`gitea:1.26.4`) |
|
||||
| **Gitea SSH** | 포트 변동 | `2222` 고정 |
|
||||
| **CI Runner** | Synology Act Runner | 6× `act_runner:latest` (Docker) |
|
||||
| **DB** | SQLite (파일 기반) | PostgreSQL 18 + SQLite (하이브리드) |
|
||||
| **웹 Admin** | 없음 | QuantEngine Blazor (.NET 10, MudBlazor) |
|
||||
| **리버스 프록시** | Synology 내장 | Nginx (도메인 기반 분기 - 홈페이지, Gitea, Quant) |
|
||||
| **보안** | DSM 방화벽 | fail2ban + SSH 공개키 + 서비스 로컬바인드 |
|
||||
| **시크릿 관리** | `.secrets/kis_real.env` | `/opt/stacks/gitea/.env` |
|
||||
| **OS** | Synology DSM 7.x | Ubuntu 26.04 LTS |
|
||||
| **타임존** | (설정 의존) | `Asia/Seoul` (NTP 동기화) |
|
||||
|
||||
## 12. 운영 명령 치트시트
|
||||
|
||||
### 서비스 관리
|
||||
|
||||
```bash
|
||||
# QuantEngine
|
||||
sudo systemctl status quantengine
|
||||
sudo systemctl restart quantengine
|
||||
sudo journalctl -u quantengine -f
|
||||
|
||||
# Gitea
|
||||
cd /opt/stacks/gitea && docker compose up -d
|
||||
docker compose logs -f gitea
|
||||
|
||||
# Nginx
|
||||
sudo systemctl reload nginx
|
||||
sudo nginx -t
|
||||
|
||||
# PostgreSQL
|
||||
sudo systemctl status postgresql@18-main
|
||||
sudo -u postgres psql
|
||||
|
||||
# Docker 전체 상태
|
||||
docker ps -a
|
||||
```
|
||||
|
||||
### QuantEngine 배포
|
||||
|
||||
```bash
|
||||
# 1. 새 배포 디렉토리 생성
|
||||
DEPLOY_DIR=~/deployments/quantengine_$(date +%Y%m%d_%H%M%S)
|
||||
mkdir -p "$DEPLOY_DIR"
|
||||
|
||||
# 2. 빌드 산출물 복사 (로컬에서 scp 또는 CI에서)
|
||||
scp -r publish/* kjh2064@178.104.200.7:"$DEPLOY_DIR"/
|
||||
|
||||
# 3. symlink 교체
|
||||
ln -sfn "$DEPLOY_DIR" ~/quantengine_active
|
||||
|
||||
# 4. 서비스 재시작
|
||||
sudo systemctl restart quantengine
|
||||
sudo systemctl status quantengine
|
||||
```
|
||||
|
||||
### Gitea Act Runner 등록
|
||||
|
||||
```bash
|
||||
# 새 러너 등록 (Gitea 웹 → Settings → Actions → Runners에서 토큰 복사)
|
||||
docker run -d \
|
||||
--name gitea-runner-N \
|
||||
--restart unless-stopped \
|
||||
--network gitea_default \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
gitea/act_runner:latest
|
||||
```
|
||||
|
||||
### SSH 접속
|
||||
|
||||
```bash
|
||||
# Windows 로컬에서
|
||||
ssh kjh2064@178.104.200.7
|
||||
|
||||
# Gitea Git 접속
|
||||
git remote set-url origin ssh://git@178.104.200.7:2222/kjh2064/QuantEngineByItz.git
|
||||
```
|
||||
|
||||
## 13. 검증 하네스
|
||||
|
||||
### 13.1. 서버 헬스 체크
|
||||
|
||||
```bash
|
||||
ssh kjh2064@178.104.200.7 "
|
||||
echo '=== Services ==='
|
||||
systemctl is-active quantengine nginx docker postgresql@18-main fail2ban
|
||||
echo '=== Docker ==='
|
||||
docker ps --format '{{.Names}}: {{.Status}}'
|
||||
echo '=== Disk ==='
|
||||
df -h /
|
||||
echo '=== Memory ==='
|
||||
free -h | head -2
|
||||
"
|
||||
```
|
||||
|
||||
**기대 결과**:
|
||||
- 5개 서비스 모두 `active`
|
||||
- Docker 컨테이너 7개 (gitea + runner ×6) `Up`
|
||||
- 디스크 사용률 < 80%
|
||||
- 메모리 가용 > 1 GiB
|
||||
|
||||
### 13.2. 엔드포인트 접근 확인
|
||||
|
||||
```bash
|
||||
# Gitea Web
|
||||
curl -s -o /dev/null -w "%{http_code}" http://178.104.200.7/
|
||||
# 기대: 200
|
||||
|
||||
# QuantEngine
|
||||
curl -s -o /dev/null -w "%{http_code}" http://178.104.200.7/quant/
|
||||
# 기대: 200
|
||||
|
||||
# Gitea SSH
|
||||
ssh -T -p 2222 git@178.104.200.7 2>&1 | head -1
|
||||
# 기대: "Hi there, ..." Gitea 응답
|
||||
```
|
||||
|
||||
### 13.3. data_feed 프로젝트 마이그레이션 체크리스트
|
||||
|
||||
- [ ] 프로젝트 경로 결정 및 clone
|
||||
- [ ] Python venv에 프로젝트 의존성 설치 (`pip install -r requirements.txt`)
|
||||
- [ ] KIS 시크릿 설정 (`~/.secrets/kis_real.env`)
|
||||
- [ ] crontab 또는 systemd timer 등록
|
||||
- [ ] `GatherTradingData.json` 동기화 경로 확정
|
||||
- [ ] SQLite canonical DB 경로 확정
|
||||
- [ ] CI 워크플로우 러너 라벨 확인
|
||||
- [ ] GAS 배포 스크립트 서버 경로 업데이트
|
||||
|
||||
---
|
||||
|
||||
> **수집 일시**: 2026-06-26 09:55 KST
|
||||
> **수집 방법**: `ssh kjh2064@178.104.200.7` 라이브 명령 실행
|
||||
> **provenance**: 모든 값은 서버 실시간 명령 출력에서 추출. 임의 값 없음.
|
||||
@@ -0,0 +1,228 @@
|
||||
# TaxBaik 배포 가이드
|
||||
|
||||
## 서버 초기 설정
|
||||
|
||||
### 1. PostgreSQL 데이터베이스 생성
|
||||
|
||||
```bash
|
||||
ssh kjh2064@178.104.200.7
|
||||
|
||||
# PostgreSQL 접속
|
||||
sudo -u postgres psql
|
||||
|
||||
# 데이터베이스 및 사용자 생성
|
||||
CREATE USER taxbaik WITH PASSWORD 'secure_password_here';
|
||||
CREATE DATABASE taxbaikdb OWNER taxbaik;
|
||||
GRANT ALL PRIVILEGES ON DATABASE taxbaikdb TO taxbaik;
|
||||
\q
|
||||
```
|
||||
|
||||
### 2. 환경 변수 설정
|
||||
|
||||
**Web 서비스** (`/etc/systemd/system/taxbaik.service`, 백엔드 전용):
|
||||
```ini
|
||||
[Service]
|
||||
Environment=ASPNETCORE_ENVIRONMENT=Production
|
||||
Environment=ASPNETCORE_URLS=http://127.0.0.1:5004
|
||||
Environment=ConnectionStrings__Default=Host=localhost;Database=taxbaikdb;Username=taxbaik;Password=your_secure_password
|
||||
```
|
||||
|
||||
**프록시 서비스** (`/etc/systemd/system/taxbaik-proxy.service`, 5001 진입점):
|
||||
```ini
|
||||
[Service]
|
||||
ExecStart=/usr/bin/dotnet TaxBaik.Proxy.dll
|
||||
WorkingDirectory=/home/kjh2064/taxbaik_active
|
||||
Restart=always
|
||||
```
|
||||
|
||||
### 3. systemd 서비스 파일 설치
|
||||
|
||||
```bash
|
||||
sudo cp deploy/taxbaik.service /etc/systemd/system/
|
||||
sudo cp deploy/taxbaik-proxy.service /etc/systemd/system/
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable taxbaik
|
||||
sudo systemctl enable taxbaik-proxy
|
||||
```
|
||||
|
||||
### 4. Nginx 설정
|
||||
|
||||
```bash
|
||||
# Nginx 도메인 기반 가상 호스트 설정 복사
|
||||
sudo cp deploy/nginx-taxbaik-domains.conf /etc/nginx/sites-available/taxbaik-domains.conf
|
||||
|
||||
# 기존 설정(IP 기반 및 default) 활성화 해제
|
||||
sudo rm -f /etc/nginx/sites-enabled/default
|
||||
sudo rm -f /etc/nginx/sites-enabled/gitea-ip.conf
|
||||
|
||||
# 새 설정 활성화 (심링크 생성)
|
||||
sudo ln -sfn /etc/nginx/sites-available/taxbaik-domains.conf /etc/nginx/sites-enabled/taxbaik-domains.conf
|
||||
|
||||
# 설정 문법 테스트 및 Nginx 서비스 리로드
|
||||
sudo nginx -t
|
||||
sudo systemctl reload nginx
|
||||
```
|
||||
|
||||
## 배포 프로세스
|
||||
|
||||
### Gitea Actions 준비
|
||||
|
||||
1. Gitea 저장소 Secrets 추가:
|
||||
- `DEPLOY_USER`: `kjh2064`
|
||||
- `DEPLOY_HOST`: `178.104.200.7`
|
||||
- `DEPLOY_SSH_KEY_B64`: base64로 인코딩한 SSH 개인키
|
||||
- `TAXBAIK_ADMIN_TEST_PASSWORD`: 배포 검증용 관리자 비밀번호
|
||||
- `Admin__PasswordResetToken`: 관리자 비밀번호 재설정 API용 서버 비밀값
|
||||
|
||||
2. 배포 워크플로우는 자동으로 실행:
|
||||
```
|
||||
master 브랜치 push → build → test → publish → restart → health check → Playwright
|
||||
```
|
||||
|
||||
수동 배포는 사용하지 않습니다. `deploy_gb.sh`는 `TAXBAIK_DEPLOY_FROM_CI=1`이 없으면 즉시 종료하므로, 배포는 반드시 Gitea Actions에서만 실행됩니다.
|
||||
|
||||
## 마이그레이션 자동 실행
|
||||
|
||||
애플리케이션 시작시 자동으로 마이그레이션이 실행됩니다:
|
||||
|
||||
1. `schema_migrations` 테이블 생성 (없으면)
|
||||
2. 실행된 마이그레이션 확인
|
||||
3. 미실행 마이그레이션 순서대로 실행
|
||||
|
||||
로그 확인:
|
||||
```bash
|
||||
journalctl -u taxbaik -n 50
|
||||
```
|
||||
|
||||
## 검증
|
||||
|
||||
### E2E 테스트
|
||||
|
||||
```bash
|
||||
# 공개 사이트 접근
|
||||
curl -I http://178.104.200.7/taxbaik/
|
||||
|
||||
# 관리자 로그인 페이지
|
||||
curl -I http://178.104.200.7/taxbaik/admin/login
|
||||
|
||||
# 로그인 API 확인
|
||||
curl -X POST http://178.104.200.7/taxbaik/api/auth/login \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"username\":\"admin\",\"password\":\"<TAXBAIK_ADMIN_TEST_PASSWORD>\"}"
|
||||
|
||||
# Playwright 브라우저 검증
|
||||
npm run test:e2e
|
||||
|
||||
# 필요한 경우 개별 테스트 실행
|
||||
npx playwright test tests/e2e/admin-login.spec.ts
|
||||
npx playwright test tests/e2e/admin-smoke.spec.ts
|
||||
npx playwright test tests/e2e/public-smoke.spec.ts
|
||||
npx playwright test tests/e2e/blog-seo.spec.ts
|
||||
npx playwright test tests/e2e/contact-submit.spec.ts
|
||||
```
|
||||
|
||||
### 블로그 포스트 확인
|
||||
|
||||
```bash
|
||||
# 초기 5개 포스트 확인
|
||||
curl http://178.104.200.7/taxbaik/blog
|
||||
|
||||
# 첫 번째 포스트 상세
|
||||
curl http://178.104.200.7/taxbaik/blog/accountant-mistakes-5
|
||||
```
|
||||
|
||||
## 롤백
|
||||
|
||||
```bash
|
||||
# 이전 버전으로 복귀
|
||||
ssh kjh2064@178.104.200.7
|
||||
|
||||
# 이전 배포 디렉토리 확인
|
||||
ls -la ~/deployments/ | grep taxbaik
|
||||
|
||||
# 심링크 변경 (예: 이전 버전이 taxbaik_20260626_140000)
|
||||
ln -sfn ~/deployments/taxbaik_20260626_140000 ~/taxbaik_active
|
||||
sudo systemctl restart taxbaik-proxy
|
||||
sudo systemctl restart taxbaik
|
||||
```
|
||||
|
||||
## 모니터링
|
||||
|
||||
### 서비스 상태 확인
|
||||
|
||||
```bash
|
||||
ssh kjh2064@178.104.200.7
|
||||
|
||||
# 서비스 상태
|
||||
systemctl status taxbaik taxbaik-proxy
|
||||
|
||||
# 포트 확인
|
||||
netstat -tlnp | grep -E '5001|5004'
|
||||
|
||||
# 프로세스 확인
|
||||
ps aux | grep TaxBaik
|
||||
```
|
||||
|
||||
### 성능 모니터링
|
||||
|
||||
```bash
|
||||
# Nginx 프록시 로그
|
||||
tail -f /var/log/nginx/access.log | grep taxbaik
|
||||
|
||||
# 애플리케이션 로그
|
||||
journalctl -u taxbaik -f
|
||||
```
|
||||
|
||||
## 트러블슈팅
|
||||
|
||||
| 증상 | 원인 | 해결 |
|
||||
|------|------|------|
|
||||
| 404 /taxbaik | Nginx 설정 미적용 | `sudo nginx -t && sudo systemctl reload nginx` |
|
||||
| Blazor WebSocket 안 됨 | `/taxbaik` location에 `proxy_http_version 1.1`, `Upgrade`, `Connection \"Upgrade\"` 헤더가 모두 있는지 확인 |
|
||||
| DB 연결 오류 | 환경 변수 미설정 | systemd service 파일의 ConnectionStrings__Default 확인 |
|
||||
| 503 Service Unavailable | 백엔드 또는 프록시 미시작 | `sudo systemctl restart taxbaik-proxy taxbaik` |
|
||||
| 마이그레이션 실패 | DB 권한 문제 | `GRANT ALL PRIVILEGES ON DATABASE taxbaikdb TO taxbaik;` |
|
||||
|
||||
## 운영 복구 순서
|
||||
|
||||
```bash
|
||||
ssh kjh2064@178.104.200.7
|
||||
sudo cp /home/kjh2064/taxbaik.service /etc/systemd/system/taxbaik.service
|
||||
sudo cp /home/kjh2064/taxbaik-proxy.service /etc/systemd/system/taxbaik-proxy.service
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl restart taxbaik-proxy
|
||||
sudo systemctl restart taxbaik
|
||||
curl -I http://127.0.0.1:5001/taxbaik/admin/login
|
||||
```
|
||||
|
||||
## 원라인 점검
|
||||
|
||||
```bash
|
||||
ssh kjh2064@178.104.200.7 'systemctl status taxbaik taxbaik-proxy --no-pager --lines=3 && ss -tlnp | grep -E ":5001 |:5004 " && curl -fsSI http://127.0.0.1:5001/taxbaik/admin/login && curl -fsS http://127.0.0.1:5001/taxbaik/favicon.svg >/dev/null && curl -fsS http://127.0.0.1:5001/taxbaik/robots.txt >/dev/null'
|
||||
```
|
||||
|
||||
## 초기 데이터
|
||||
|
||||
### 관리자 계정
|
||||
|
||||
- **username**: `admin`
|
||||
- **password**: `<TAXBAIK_ADMIN_TEST_PASSWORD>` (운영 검증용 비밀번호, Secrets로 관리)
|
||||
- 초기 로그인 후 비밀번호 즉시 변경 권장
|
||||
|
||||
### 블로그 포스트
|
||||
|
||||
V003 마이그레이션에서 5개 포스트 자동 생성:
|
||||
1. 사업자 기장 시 자주 하는 실수 5가지
|
||||
2. 부동산 양도세 계산하기
|
||||
3. 프리랜서를 위한 종합소득세 신고
|
||||
4. 부가가치세 간이과세 vs 일반과세
|
||||
5. 가족 자산 증여세 절세 방법
|
||||
|
||||
## 차후 작업
|
||||
|
||||
- [ ] SSL 인증서 적용 (Let's Encrypt)
|
||||
- [ ] 도메인 연결 (현재는 IP 기반)
|
||||
- [ ] 관리자 인증 보안 고도화 (rate limit, 비밀번호 교체 절차)
|
||||
- [ ] 블로그 포스트 수정 화면 완성
|
||||
- [ ] Naver/Google Search Console 등록
|
||||
- [ ] 운영 관리자 비밀번호를 초기 시드값에서 교체하고 `TAXBAIK_ADMIN_TEST_PASSWORD` 갱신
|
||||
@@ -0,0 +1,127 @@
|
||||
# TaxBaik 배포 요약
|
||||
|
||||
> 이 문서는 현재 WBS 기준의 검증 문서가 아니라, 과거 배포 요약의 기록이다.
|
||||
> 최신 상태는 `ROADMAP_WBS.md`와 CI 로그를 기준으로 판단한다.
|
||||
|
||||
## 📊 과거 기록 현황
|
||||
|
||||
### ⚠️ 과거 기준 기록
|
||||
|
||||
| 단계 | 항목 | 상태 |
|
||||
|------|------|------|
|
||||
| W0 | 프로젝트 기반 구축 | 과거 기록 |
|
||||
| W1 | LLM 개발 지침 (CLAUDE.md) | 과거 기록 |
|
||||
| W2 | 도메인/인프라/서비스 레이어 | 과거 기록 |
|
||||
| **W3** | **공개 홈페이지 (Razor Pages SSR)** | 과거 기록 |
|
||||
| **W4** | **관리자 백오피스 (Blazor Server)** | 과거 기록 |
|
||||
| **W5** | **스타일링 및 모바일 UX** | 과거 기록 |
|
||||
| **W6** | **출시 준비 (E2E 테스트)** | 과거 기록 |
|
||||
|
||||
---
|
||||
|
||||
## 🚀 과거 배포 엔드포인트 기록
|
||||
|
||||
### 공개 사이트
|
||||
- 🏠 **홈페이지**: http://178.104.200.7/taxbaik
|
||||
- 📋 **서비스**: http://178.104.200.7/taxbaik/services
|
||||
- 👤 **소개**: http://178.104.200.7/taxbaik/about
|
||||
- 📞 **연락처**: http://178.104.200.7/taxbaik/contact
|
||||
- 📝 **블로그**: http://178.104.200.7/taxbaik/blog
|
||||
|
||||
### 관리자
|
||||
- 🔐 **로그인**: http://178.104.200.7/taxbaik/admin/login
|
||||
- 📊 **대시보드**: http://178.104.200.7/taxbaik/admin/dashboard
|
||||
- 계정 정보는 문서에 기록하지 않고 Gitea Secrets 또는 서버 환경변수로만 관리한다.
|
||||
|
||||
---
|
||||
|
||||
## 📁 과거 기술 구성 기록
|
||||
|
||||
### 공개 사이트
|
||||
- **기술**: ASP.NET Core 10 Razor Pages (SSR)
|
||||
- **SEO**: Server-Side Rendering, 메타 태그, 사이트맵
|
||||
- **데이터**: PostgreSQL 18, Dapper ORM
|
||||
- **스타일**: Bootstrap 5, CSS 변수 시스템
|
||||
- **모바일**: 반응형, 고정 CTA 바
|
||||
|
||||
### 관리자 백오피스
|
||||
- **기술**: Blazor Server (Interactive)
|
||||
- **UI**: MudBlazor 컴포넌트
|
||||
- **인증**: 쿠키 기반
|
||||
- **기능**: 대시보드, 문의 관리
|
||||
|
||||
### 인프라
|
||||
- **배포**: 심링크 기반 무중단 배포
|
||||
- **마이그레이션**: 파일 시스템 기반 자동 실행
|
||||
- **라우팅**: Nginx reverse proxy
|
||||
- **프로세스**: systemd 서비스
|
||||
|
||||
---
|
||||
|
||||
## 📊 과거 데이터베이스 기록
|
||||
|
||||
### 초기 데이터
|
||||
- 5개 카테고리: 사업자세무, 부동산세금, 종합소득세, 부가가치세, 가족자산증여
|
||||
- 5개 블로그 포스트: 초기 콘텐츠 포함
|
||||
- 관리자 계정: 비밀번호는 문서화하지 않는다.
|
||||
|
||||
---
|
||||
|
||||
## 🔧 과거 배포 절차 기록
|
||||
|
||||
1. **로컬 빌드**
|
||||
```bash
|
||||
dotnet publish TaxBaik.Web -c Release -o ./publish/web
|
||||
dotnet publish TaxBaik.Admin -c Release -o ./publish/admin
|
||||
```
|
||||
|
||||
2. **서버 배포** (자동)
|
||||
- tar 압축 → SCP 전송
|
||||
- 심링크 업데이트
|
||||
- 프로세스 재시작
|
||||
- 무중단 배포 (Shadow Copy)
|
||||
|
||||
3. **검증**
|
||||
- HTTP 상태 코드 확인
|
||||
- 엔드포인트 응답성 테스트
|
||||
- 데이터 일관성 확인
|
||||
|
||||
---
|
||||
|
||||
## 📝 Git 커밋 히스토리
|
||||
|
||||
```
|
||||
fc54ba5 기능: W4 관리자 백오피스 기본 완성
|
||||
66eb4ae 기능: W3 공개 홈페이지 완성 (Razor Pages SSR)
|
||||
25a7fe3 수정: Blazor Server 라우팅 개선
|
||||
e7e01d0 마이그레이션 및 보안 수정
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✨ 주요 특징
|
||||
|
||||
- SEO 항목 (Server-Side Rendering)
|
||||
- 심링크 기반 배포
|
||||
- 반응형 모바일 UI
|
||||
- 한국어 UI
|
||||
- 자동 마이그레이션
|
||||
- 인증 항목
|
||||
- 레이어 구조
|
||||
- 기록용 요약일 뿐, 현재 완료 판정 기준은 아니다.
|
||||
|
||||
---
|
||||
|
||||
## 🎯 향후 개선 후보
|
||||
|
||||
1. BCrypt 실제 인증 개선
|
||||
2. Blog CRUD 관리자 기능 완성
|
||||
3. 문의 상태 변경 기능
|
||||
4. 메일 알림 통합
|
||||
5. Google Analytics 통합
|
||||
6. Sentry 모니터링
|
||||
|
||||
---
|
||||
|
||||
**기록일**: 2026-06-26
|
||||
**상태**: 기록용 요약
|
||||
@@ -0,0 +1,169 @@
|
||||
# Docker로 TaxBaik 로컬 실행하기
|
||||
|
||||
## 1. 사전 요구사항
|
||||
|
||||
- Docker Desktop 설치
|
||||
- Docker Compose 설치
|
||||
- 4GB+ 메모리 여유
|
||||
|
||||
## 2. 빌드 및 실행
|
||||
|
||||
```bash
|
||||
# 1단계: 빌드 (이미 완료됨)
|
||||
cd C:\Temp\taxbaik
|
||||
dotnet publish TaxBaik.Web -c Release -o ./publish/web
|
||||
|
||||
# 2단계: Docker Compose 실행
|
||||
docker-compose up -d
|
||||
|
||||
# 3단계: 상태 확인
|
||||
docker-compose ps
|
||||
```
|
||||
|
||||
## 3. 접근 방법
|
||||
|
||||
### 공개 사이트 (Razor Pages)
|
||||
- **URL**: http://localhost:5001/taxbaik
|
||||
- **기능**:
|
||||
- 홈페이지 보기
|
||||
- 블로그 검색
|
||||
- 상담 신청 폼
|
||||
|
||||
### 관리자 백오피스 (Blazor Server)
|
||||
- **URL**: http://localhost:5001/taxbaik/admin/login
|
||||
- **초기 계정**:
|
||||
- username: `admin`
|
||||
- password: `<TAXBAIK_ADMIN_TEST_PASSWORD>`
|
||||
- **기능**:
|
||||
- 대시보드 확인
|
||||
- 블로그 관리
|
||||
- 문의 조회
|
||||
|
||||
## 4. 데이터베이스 접근
|
||||
|
||||
### PostgreSQL CLI 접속
|
||||
```bash
|
||||
docker-compose exec postgres psql -U taxbaik -d taxbaikdb
|
||||
```
|
||||
|
||||
### 쿼리 예시
|
||||
```sql
|
||||
-- 테이블 확인
|
||||
\dt
|
||||
|
||||
-- 마이그레이션 확인
|
||||
SELECT * FROM schema_migrations;
|
||||
|
||||
-- 블로그 포스트 확인
|
||||
SELECT id, title, is_published FROM blog_posts;
|
||||
|
||||
-- 문의 확인
|
||||
SELECT id, name, phone, status FROM inquiries;
|
||||
|
||||
-- 관리자 확인
|
||||
SELECT username FROM admin_users;
|
||||
```
|
||||
|
||||
## 5. 로그 확인
|
||||
|
||||
```bash
|
||||
# Web 앱 로그
|
||||
docker-compose logs -f taxbaik-web
|
||||
|
||||
# 데이터베이스 로그
|
||||
docker-compose logs -f postgres
|
||||
```
|
||||
|
||||
## 6. E2E 테스트
|
||||
|
||||
### 6.1 공개 사이트 테스트
|
||||
```bash
|
||||
# 홈페이지 접근
|
||||
curl -I http://localhost:5001/taxbaik/
|
||||
# 예상: 200 OK
|
||||
|
||||
# 블로그 목록
|
||||
curl http://localhost:5001/taxbaik/blog | grep -o "title>[^<]*" | head -5
|
||||
|
||||
# 블로그 상세 (accountant-mistakes-5)
|
||||
curl http://localhost:5001/taxbaik/blog/accountant-mistakes-5 | grep -o "og:title[^>]*"
|
||||
```
|
||||
|
||||
### 6.2 문의 폼 제출 테스트
|
||||
```bash
|
||||
curl -X POST http://localhost:5001/taxbaik/contact \
|
||||
-H "Content-Type: application/x-www-form-urlencoded" \
|
||||
-d "name=테스트&phone=010-1234-5678&service_type=사업자세무&message=테스트 문의&__RequestVerificationToken="
|
||||
```
|
||||
|
||||
### 6.3 관리자 테스트
|
||||
```bash
|
||||
# 로그인 페이지
|
||||
curl -I http://localhost:5001/taxbaik/admin/login
|
||||
# 예상: 200 OK
|
||||
|
||||
# 로그인 (쿠키 저장)
|
||||
curl -c cookies.txt -X POST http://localhost:5001/taxbaik/admin/login \
|
||||
-H "Content-Type: application/x-www-form-urlencoded" \
|
||||
-d "username=admin&password=<TAXBAIK_ADMIN_TEST_PASSWORD>"
|
||||
|
||||
# 대시보드 접근 (쿠키 사용)
|
||||
curl -b cookies.txt http://localhost:5001/taxbaik/admin/dashboard
|
||||
```
|
||||
|
||||
## 7. 종료 및 정리
|
||||
|
||||
```bash
|
||||
# 컨테이너 중지
|
||||
docker-compose down
|
||||
|
||||
# 볼륨 포함 삭제 (데이터베이스 초기화)
|
||||
docker-compose down -v
|
||||
|
||||
# 이미지 삭제
|
||||
docker rmi taxbaik-web
|
||||
```
|
||||
|
||||
## 8. 트러블슈팅
|
||||
|
||||
| 문제 | 해결 방법 |
|
||||
|------|----------|
|
||||
| 포트 5001 사용 중 | `netstat -ano \| findstr :5001` 후 프로세스 종료 |
|
||||
| 데이터베이스 연결 실패 | `docker-compose logs postgres` 로그 확인 |
|
||||
| 마이그레이션 오류 | `docker-compose down -v` 후 재시작 |
|
||||
| 메모리 부족 | Docker Desktop 설정에서 메모리 증가 |
|
||||
|
||||
## 9. 성능 모니터링
|
||||
|
||||
```bash
|
||||
# 컨테이너 리소스 사용량
|
||||
docker stats
|
||||
|
||||
# 네트워크 확인
|
||||
docker network ls
|
||||
docker network inspect taxbaik_default
|
||||
```
|
||||
|
||||
## 10. 데이터 검증
|
||||
|
||||
### 초기 데이터 확인
|
||||
```bash
|
||||
# 카테고리
|
||||
docker-compose exec postgres psql -U taxbaik -d taxbaikdb \
|
||||
-c "SELECT * FROM categories;"
|
||||
|
||||
# 블로그 포스트
|
||||
docker-compose exec postgres psql -U taxbaik -d taxbaikdb \
|
||||
-c "SELECT title, is_published FROM blog_posts;"
|
||||
|
||||
# 관리자
|
||||
docker-compose exec postgres psql -U taxbaik -d taxbaikdb \
|
||||
-c "SELECT username FROM admin_users;"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**상태 확인 URL**:
|
||||
- 공개 사이트: http://localhost:5001/taxbaik
|
||||
- 관리자: http://localhost:5001/taxbaik/admin/login
|
||||
- 데이터베이스: localhost:5432
|
||||
@@ -0,0 +1,280 @@
|
||||
# TaxBaik 과거 완료 요약 기록
|
||||
|
||||
**프로젝트**: 세무사 백원숙 전문성 표현 홈페이지
|
||||
**기록일**: 2026-06-26
|
||||
**상태**: 과거 기록. 현재 완료 판정은 `ROADMAP_WBS.md`와 CI/Playwright 로그를 기준으로 한다.
|
||||
|
||||
---
|
||||
|
||||
## 📌 프로젝트 개요
|
||||
|
||||
### 비즈니스 목표 기록
|
||||
- 온라인 전문성 표현
|
||||
- 블로그 SEO 유입
|
||||
- 전국 고객 확보
|
||||
|
||||
### 핵심 포지셔닝
|
||||
> "사업자 세금 + 부동산 + 가족자산 = 맞춤형 세무 파트너"
|
||||
|
||||
---
|
||||
|
||||
## 🎯 과거 기준 작업 기록 (W0~W6)
|
||||
|
||||
| 단계 | 작업 | 상태 | 커밋 수 |
|
||||
|------|------|------|--------|
|
||||
| **W0** | 프로젝트 기반 구축 | 과거 기록 | 3 |
|
||||
| **W1** | LLM 개발 지침 작성 | 과거 기록 | 1 |
|
||||
| **W2** | Domain/Infrastructure/Application | 과거 기록 | 2 |
|
||||
| **W3** | 공개 홈페이지 (Razor Pages) | 과거 기록 | 4 |
|
||||
| **W4** | 관리자 백오피스 (Blazor) | 과거 기록 | 3 |
|
||||
| **W5** | 스타일링 & 성능 최적화 | 과거 기록 | 1 |
|
||||
| **W6** | 배포 준비 & CI/CD | 과거 기록 | 5 |
|
||||
|
||||
**총 커밋**: 19개 (모두 한국어)
|
||||
|
||||
---
|
||||
|
||||
## 📦 기술 스택
|
||||
|
||||
### 백엔드
|
||||
- **프레임워크**: ASP.NET Core 8
|
||||
- **언어**: C# 11
|
||||
- **데이터 접근**: Dapper 2.1.15
|
||||
- **데이터베이스**: PostgreSQL 18.4
|
||||
- **의존성 주입**: Microsoft.Extensions.DependencyInjection
|
||||
|
||||
### 프론트엔드
|
||||
- **공개 사이트**: Razor Pages (SSR)
|
||||
- **관리자**: Blazor Server
|
||||
- **UI 컴포넌트**: MudBlazor 6.9+
|
||||
- **스타일**: Bootstrap 5 + Custom CSS
|
||||
|
||||
### 인프라
|
||||
- **웹 서버**: Nginx (리버스 프록시)
|
||||
- **OS**: Ubuntu 26.04
|
||||
- **배포 자동화**: Gitea Actions CI/CD
|
||||
- **서비스 관리**: systemd
|
||||
|
||||
---
|
||||
|
||||
## 📂 산출물 목록
|
||||
|
||||
### 1. 코드
|
||||
```
|
||||
TaxBaik.Domain/ 11 KB (순수 엔티티)
|
||||
TaxBaik.Infrastructure/ 45 KB (Dapper + DB)
|
||||
TaxBaik.Application/ 17 KB (Services)
|
||||
TaxBaik.Web/ 82 KB (Razor Pages)
|
||||
TaxBaik.Admin/ 95 KB (Blazor Server)
|
||||
```
|
||||
|
||||
**총 코드량**: ~50,000줄 (주석 제외)
|
||||
|
||||
### 2. 문서
|
||||
| 파일 | 용도 | 라인 수 |
|
||||
|------|------|--------|
|
||||
| README.md | 프로젝트 개요 | 336 |
|
||||
| CLAUDE.md | 개발 지침 | 500+ |
|
||||
| DEPLOYMENT_GUIDE.md | 배포 가이드 | 400+ |
|
||||
| PRODUCTION_CHECKLIST.md | 배포 체크리스트 | 350+ |
|
||||
| SERVER_SETUP.sh | 서버 설치 스크립트 | 100 |
|
||||
|
||||
### 3. 설정 파일
|
||||
- `.gitea/workflows/deploy.yml` — CI/CD 자동화
|
||||
- `deploy/taxbaik.service` — Web 서비스
|
||||
- `deploy/taxbaik-admin.service` — Admin 서비스
|
||||
- `deploy/nginx-taxbaik-locations.conf` — Nginx 설정
|
||||
|
||||
### 4. 데이터베이스
|
||||
- `db/migrations/V001__InitialSchema.sql` — 스키마 (5개 테이블)
|
||||
- `db/migrations/V002__SeedData.sql` — 초기 데이터
|
||||
- `db/migrations/V003__SeedAdminAndBlogPosts.sql` — 블로그 5개 + 관리자
|
||||
|
||||
---
|
||||
|
||||
## ✨ 주요 기능
|
||||
|
||||
### 공개 사이트
|
||||
- SEO 블로그
|
||||
- 온라인 상담 신청 폼
|
||||
- 반응형 디자인
|
||||
- 성능 최적화 항목
|
||||
|
||||
### 관리자 백오피스
|
||||
- 대시보드
|
||||
- 블로그 관리
|
||||
- 문의 관리
|
||||
- 사이트 설정
|
||||
|
||||
### 보안 & 성능
|
||||
- SQL Injection 방지 항목
|
||||
- 인증/인가 항목
|
||||
- gzip 응답 압축
|
||||
- 이미지 lazy load
|
||||
- 폰트 preconnect
|
||||
|
||||
---
|
||||
|
||||
## 🚀 배포 자동화
|
||||
|
||||
### CI/CD 파이프라인
|
||||
```
|
||||
master 브랜치 push
|
||||
↓
|
||||
Gitea Actions 트리거
|
||||
↓
|
||||
1. dotnet build -c Release
|
||||
2. dotnet publish
|
||||
3. rsync 업로드
|
||||
4. 심링크 스왑
|
||||
5. systemctl restart
|
||||
↓
|
||||
배포 기록 생성
|
||||
```
|
||||
|
||||
### 자동 마이그레이션
|
||||
```
|
||||
앱 시작
|
||||
↓
|
||||
MigrationRunner 실행
|
||||
↓
|
||||
schema_migrations 테이블 확인
|
||||
↓
|
||||
미실행 마이그레이션 자동 실행
|
||||
↓
|
||||
DB 준비 기록 생성
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 과거 코드 품질 기록
|
||||
|
||||
| 항목 | 상태 | 세부 |
|
||||
|------|------|------|
|
||||
| **빌드** | 과거 기록 | 최신 상태는 CI 로그 기준 |
|
||||
| **보안** | 과거 기록 | 최신 상태는 코드 리뷰와 테스트 기준 |
|
||||
| **성능** | 과거 기록 | 최신 상태는 WBS 검증 기준 |
|
||||
| **SEO** | 과거 기록 | 최신 상태는 `blog-seo` Playwright 기준 |
|
||||
| **테스트** | 과거 기록 | 최신 상태는 Playwright/CI 기준 |
|
||||
| **문서** | 과거 기록 | 최신 상태는 `ROADMAP_WBS.md` 기준 |
|
||||
|
||||
---
|
||||
|
||||
## 🎯 과거 수락 기준 기록
|
||||
|
||||
### 기술적 요구사항
|
||||
- ASP.NET Core 기반
|
||||
- Dapper + PostgreSQL 사용
|
||||
- Razor Pages SSR (공개 사이트)
|
||||
- Blazor Server (관리자)
|
||||
- 계층화된 아키텍처
|
||||
- UI 문자열 한국어
|
||||
|
||||
### 기능 요구사항
|
||||
- 블로그
|
||||
- 온라인 문의 폼
|
||||
- 관리자 백오피스
|
||||
- 반응형 디자인
|
||||
- 성능 최적화
|
||||
|
||||
### 배포 요구사항
|
||||
- CI/CD 파이프라인
|
||||
- 자동 마이그레이션
|
||||
- 심링크 배포
|
||||
- systemd 서비스 파일
|
||||
- Nginx 리버스 프록시 설정
|
||||
|
||||
### 문서 요구사항
|
||||
- CLAUDE.md
|
||||
- DEPLOYMENT_GUIDE.md
|
||||
- README.md
|
||||
- 서버 설치 스크립트
|
||||
|
||||
---
|
||||
|
||||
## 📈 프로젝트 통계
|
||||
|
||||
### 코드 메트릭
|
||||
- **프로젝트**: 5개
|
||||
- **클래스**: 50+ (도메인 엔티티, 서비스, 리포지토리)
|
||||
- **메서드**: 200+
|
||||
- **테이블**: 5개
|
||||
- **마이그레이션**: 3개
|
||||
|
||||
### 커밋 통계
|
||||
- **총 커밋**: 19개
|
||||
- **언어**: 100% 한국어
|
||||
- **기간**: 1일 (집중 개발)
|
||||
|
||||
### 문서 통계
|
||||
- **문서**: 5개
|
||||
- **총 라인**: 1,500+
|
||||
- **한국어 비율**: 100%
|
||||
|
||||
---
|
||||
|
||||
## 🔗 Gitea 저장소
|
||||
|
||||
**URL**: http://178.104.200.7/kjh2064/taxbaik.git
|
||||
|
||||
**최근 커밋**:
|
||||
```
|
||||
2e08529 수정: MigrationRunner 네임스페이스 import 추가
|
||||
f129c37 문서: 최종 프로젝트 README 작성
|
||||
1c80246 수정: Gitea Actions 워크플로우 - master 브랜치 및 경로 수정
|
||||
b875538 추가: 마이그레이션 러너 및 배포 가이드
|
||||
b300cd7 완성: 빌드 성공 및 최종 통합 (W0~W6 완료)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 과거 체크리스트 기록
|
||||
|
||||
### 개발 기록
|
||||
- 코드 작성 기록
|
||||
- 로컬 빌드 기록
|
||||
- Git 커밋/푸시 기록
|
||||
|
||||
### 검증 기록
|
||||
- 아키텍처 검토 기록
|
||||
- 코드 구조 검토 기록
|
||||
- 보안 검토 기록
|
||||
- 성능 검토 기록
|
||||
- SEO 검토 기록
|
||||
|
||||
### 배포 준비
|
||||
- CI/CD 파이프라인
|
||||
- 자동 마이그레이션
|
||||
- 배포 스크립트
|
||||
- 배포 가이드
|
||||
- 모니터링 설정
|
||||
|
||||
### 문서 기록
|
||||
- README.md
|
||||
- CLAUDE.md
|
||||
- DEPLOYMENT_GUIDE.md
|
||||
- PRODUCTION_CHECKLIST.md
|
||||
- SERVER_SETUP.sh
|
||||
|
||||
---
|
||||
|
||||
## 현재 후속 기준
|
||||
|
||||
1. `ROADMAP_WBS.md`의 미완료 항목을 기준으로 작업한다.
|
||||
2. 완료 판정은 CI 배포, 배포 검증, Playwright E2E 통과 후에만 한다.
|
||||
3. 서버 수동 변경은 비상 롤백을 제외하고 금지한다.
|
||||
|
||||
---
|
||||
|
||||
## 📞 연락처
|
||||
|
||||
- **전화**: 010-4122-8268
|
||||
- **이메일**: taxbaik5668@gmail.com
|
||||
- **카카오**: http://pf.kakao.com/_xoxchTX
|
||||
- **인스타그램**: https://www.instagram.com/taxtory5668/
|
||||
|
||||
---
|
||||
|
||||
**프로젝트 상태**: 진행 중
|
||||
|
||||
이 문서는 과거 완료 요약으로 남기고, 현재 진행 상태는 `ROADMAP_WBS.md`를 따른다.
|
||||
@@ -0,0 +1,286 @@
|
||||
# TaxBaik 프로덕션 배포 체크리스트
|
||||
|
||||
**작성일**: 2026-06-26
|
||||
**상태**: 운영 기준 정비 중
|
||||
**대상**: 178.104.200.7 (Ubuntu 26.04)
|
||||
|
||||
---
|
||||
|
||||
## 📋 배포 전 (개발 환경)
|
||||
|
||||
### 1. 코드 검증
|
||||
- [ ] `dotnet build TaxBaik.sln -c Release` 성공
|
||||
- [ ] 모든 컴파일 오류 0개
|
||||
- [ ] 경고 0개 유지
|
||||
|
||||
### 2. Git 상태 확인
|
||||
- [ ] 모든 변경사항 커밋됨
|
||||
- [ ] `git status`에서 clean 상태
|
||||
- [ ] `git log --oneline | head -5` 확인
|
||||
|
||||
### 3. 발행 검증
|
||||
```bash
|
||||
dotnet publish TaxBaik.Web -c Release -o ./publish
|
||||
|
||||
# 확인
|
||||
ls -lh ./publish/TaxBaik.Web.dll
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🖥️ 서버 배포
|
||||
|
||||
### 1단계: 서버 초기 설정 (한 번만)
|
||||
|
||||
```bash
|
||||
# 1. SSH 접속
|
||||
ssh kjh2064@178.104.200.7
|
||||
|
||||
# 2. 자동 설치 스크립트 실행
|
||||
curl -s https://raw.githubusercontent.com/kjh2064/taxbaik/master/SERVER_SETUP.sh | bash
|
||||
# 또는 로컬에서 업로드
|
||||
scp deploy/SERVER_SETUP.sh kjh2064@178.104.200.7:~/
|
||||
ssh kjh2064@178.104.200.7 'bash ~/SERVER_SETUP.sh'
|
||||
|
||||
# 3. 배포 디렉토리 생성 (자동으로 진행됨)
|
||||
# ~/deployments/
|
||||
# ~/taxbaik_active
|
||||
# ~/taxbaik_active
|
||||
```
|
||||
|
||||
### 2단계: Gitea Actions 설정
|
||||
|
||||
**Gitea 저장소 Settings → Secrets 추가**:
|
||||
- `DEPLOY_USER`: `kjh2064`
|
||||
- `DEPLOY_HOST`: `178.104.200.7`
|
||||
- `DEPLOY_SSH_KEY`: SSH 개인키 (줄바꿈 포함)
|
||||
|
||||
이후 `master` 브랜치 푸시 → 자동 배포
|
||||
|
||||
---
|
||||
|
||||
## ✅ 배포 후 검증
|
||||
|
||||
### 1. 서비스 상태 확인
|
||||
|
||||
```bash
|
||||
ssh kjh2064@178.104.200.7
|
||||
|
||||
# 서비스 상태
|
||||
sudo systemctl status taxbaik
|
||||
|
||||
# 프로세스 확인
|
||||
ps aux | grep TaxBaik
|
||||
|
||||
# 포트 확인
|
||||
netstat -tlnp | grep -E '5001'
|
||||
```
|
||||
|
||||
### 2. 엔드포인트 테스트
|
||||
|
||||
```bash
|
||||
# 공개 사이트
|
||||
curl -I http://178.104.200.7/taxbaik/
|
||||
# 예상: 200 OK
|
||||
|
||||
# 관리자 로그인
|
||||
curl -I http://178.104.200.7/taxbaik/admin/login
|
||||
# 예상: 200 OK
|
||||
|
||||
# API 헬스체크
|
||||
curl http://178.104.200.7/taxbaik/api/health 2>/dev/null || echo "헬스체크 없음 (정상)"
|
||||
```
|
||||
|
||||
### 3. 데이터베이스 확인
|
||||
|
||||
```bash
|
||||
ssh kjh2064@178.104.200.7
|
||||
|
||||
# DB 연결
|
||||
psql -U taxbaik -d taxbaikdb
|
||||
|
||||
# 테이블 확인
|
||||
\dt
|
||||
|
||||
# 마이그레이션 확인
|
||||
SELECT * FROM schema_migrations;
|
||||
|
||||
# 데이터 확인
|
||||
SELECT COUNT(*) FROM blog_posts;
|
||||
SELECT COUNT(*) FROM inquiries;
|
||||
SELECT COUNT(*) FROM categories;
|
||||
```
|
||||
|
||||
### 4. 로그 확인
|
||||
|
||||
```bash
|
||||
# 웹 서비스
|
||||
journalctl -u taxbaik -n 50
|
||||
|
||||
# Nginx
|
||||
sudo tail -f /var/log/nginx/access.log | grep taxbaik
|
||||
```
|
||||
|
||||
### 5. 성능 테스트
|
||||
|
||||
```bash
|
||||
# 응답 속도 측정
|
||||
time curl http://178.104.200.7/taxbaik/ > /dev/null
|
||||
|
||||
# gzip 압축 확인
|
||||
curl -I -H "Accept-Encoding: gzip" http://178.104.200.7/taxbaik/ | grep -i encoding
|
||||
|
||||
# 예상: Content-Encoding: gzip
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📱 기능 테스트 (QA)
|
||||
|
||||
### 공개 사이트 (Web)
|
||||
|
||||
#### 홈페이지
|
||||
- [ ] http://178.104.200.7/taxbaik/ 접근 가능
|
||||
- [ ] 영웅 섹션 표시
|
||||
- [ ] 서비스 카드 표시
|
||||
- [ ] 최근 블로그 포스트 표시
|
||||
- [ ] 모바일 CTA 바 표시
|
||||
|
||||
#### 블로그
|
||||
- [ ] http://178.104.200.7/taxbaik/blog 페이지 로드
|
||||
- [ ] 5개 카테고리 탭 표시
|
||||
- [ ] 포스트 그리드 표시
|
||||
- [ ] 포스트 클릭 → 상세 페이지로 이동
|
||||
- [ ] SEO 메타 태그 확인: `curl http://178.104.200.7/taxbaik/blog/accountant-mistakes-5 | grep og:title`
|
||||
|
||||
#### 문의 폼
|
||||
- [ ] http://178.104.200.7/taxbaik/contact 페이지 로드
|
||||
- [ ] 폼 필드 표시 (이름, 전화, 이메일, 분야, 메시지)
|
||||
- [ ] 제출 버튼 활성화
|
||||
- [ ] 폼 제출 성공 (응답 메시지 표시)
|
||||
|
||||
#### 관리자 백오피스 (Admin)
|
||||
|
||||
#### 로그인
|
||||
- [ ] http://178.104.200.7/taxbaik/admin/login 접근
|
||||
- [ ] 로그인 폼 표시
|
||||
- [ ] 초기 계정 로그인
|
||||
- username: `admin`
|
||||
- password: `<TAXBAIK_ADMIN_TEST_PASSWORD>`
|
||||
|
||||
#### 대시보드
|
||||
- [ ] 로그인 후 대시보드 로드
|
||||
- [ ] KPI 카드 표시 (이번달 문의, 신규 문의 등)
|
||||
- [ ] 최근 문의 테이블 표시
|
||||
|
||||
#### 블로그 관리
|
||||
- [ ] 블로그 목록 페이지 로드
|
||||
- [ ] 5개 포스트 표시
|
||||
- [ ] 수정/삭제 버튼 활성화
|
||||
|
||||
#### 문의 관리
|
||||
- [ ] 문의 목록 페이지 로드
|
||||
- [ ] 최근 제출한 문의 표시
|
||||
- [ ] 상태 변경 기능 테스트
|
||||
|
||||
#### 사이트 설정
|
||||
- [ ] 설정 페이지 로드
|
||||
- [ ] 연락처 정보 표시
|
||||
- [ ] 설정 저장 기능 테스트
|
||||
|
||||
---
|
||||
|
||||
## 🔧 트러블슈팅
|
||||
|
||||
| 증상 | 원인 | 해결 방법 |
|
||||
|------|------|----------|
|
||||
| 404 /taxbaik | Nginx 설정 미적용 | `sudo nginx -t && sudo systemctl reload nginx` |
|
||||
| 502 Bad Gateway | 프록시 또는 백엔드 미실행 | `sudo systemctl restart taxbaik-proxy taxbaik` |
|
||||
| 503 Service Unavailable | 백엔드 충돌 또는 비밀값 누락 | 로그 확인: `journalctl -u taxbaik -n 50` |
|
||||
| DB 연결 오류 | 환경 변수 미설정 | systemd 파일의 ConnectionStrings__Default 확인 |
|
||||
| HTTPS 오류 | SSL 미구성 | 개발 환경에서는 HTTP 사용 (IP 기반) |
|
||||
| 마이그레이션 실패 | 테이블 존재 | `DROP DATABASE taxbaikdb;` 후 재시작 |
|
||||
|
||||
---
|
||||
|
||||
## 📊 모니터링
|
||||
|
||||
### 실시간 모니터링
|
||||
|
||||
```bash
|
||||
# 터미널 1: 백엔드 로그
|
||||
ssh kjh2064@178.104.200.7 'journalctl -u taxbaik -f'
|
||||
|
||||
# 터미널 2: 프록시 로그
|
||||
ssh kjh2064@178.104.200.7 'journalctl -u taxbaik-proxy -f'
|
||||
|
||||
# 터미널 3: Nginx 로그
|
||||
ssh kjh2064@178.104.200.7 'sudo tail -f /var/log/nginx/access.log | grep taxbaik'
|
||||
|
||||
# 터미널 4: 시스템 리소스
|
||||
ssh kjh2064@178.104.200.7 'watch -n 1 "ps aux | grep TaxBaik"'
|
||||
```
|
||||
|
||||
### 정기적 검사
|
||||
|
||||
```bash
|
||||
# 일일 체크는 CI 배포 후 자동 검증으로 대체
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔄 배포 후 업데이트
|
||||
|
||||
### 코드 변경 후 배포
|
||||
|
||||
```bash
|
||||
# 1. 로컬에서 커밋 및 푸시
|
||||
git add .
|
||||
git commit -m "기능: 새로운 기능 추가"
|
||||
git push origin master
|
||||
|
||||
# 2. Gitea Actions가 자동으로 배포
|
||||
```
|
||||
|
||||
### 롤백 절차
|
||||
|
||||
```bash
|
||||
# 이전 버전 목록 확인
|
||||
ssh kjh2064@178.104.200.7 'ls -la ~/deployments/ | grep taxbaik'
|
||||
|
||||
# 롤백 (예: 이전 버전이 taxbaik_20260625_100000)
|
||||
ssh kjh2064@178.104.200.7 << EOF
|
||||
ln -sfn ~/deployments/taxbaik_20260625_100000 ~/taxbaik_active
|
||||
sudo systemctl restart taxbaik-proxy
|
||||
sudo systemctl restart taxbaik
|
||||
EOF
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✨ 수락 기준
|
||||
|
||||
배포가 성공하려면 다음을 모두 만족해야 합니다:
|
||||
|
||||
- [ ] 모든 엔드포인트 HTTP 200 응답
|
||||
- [ ] 데이터베이스 마이그레이션 완료 (schema_migrations 테이블 확인)
|
||||
- [ ] 초기 5개 블로그 포스트 DB에 존재
|
||||
- [ ] 로그인 기능 정상 (admin/<TAXBAIK_ADMIN_TEST_PASSWORD>)
|
||||
- [ ] 문의 폼 제출 → DB 저장 확인
|
||||
- [ ] Nginx 프록시 정상 작동
|
||||
- [ ] 응답 gzip 압축 확인
|
||||
- [ ] 관리자 페이지 WebSocket 연결 성공
|
||||
|
||||
---
|
||||
|
||||
## 📞 긴급 연락처
|
||||
|
||||
**문제 발생 시**:
|
||||
- 로그 확인: `journalctl -u taxbaik`
|
||||
- 포트 확인: `netstat -tlnp`
|
||||
- DB 상태: `psql -U taxbaik -d taxbaikdb -c "SELECT 1;"`
|
||||
- 관리자 연락: kjh2064@gmail.com
|
||||
|
||||
---
|
||||
|
||||
**다음 단계**: 체크리스트 완료 후 프로덕션 운영 시작
|
||||
@@ -0,0 +1,567 @@
|
||||
# TaxBaik 개선 로드맵 WBS
|
||||
|
||||
이 문서는 "완료 보고"가 아니라 검증 가능한 작업 목록이다. 각 WBS는 성공 기준을 통과해야 완료로 본다.
|
||||
|
||||
---
|
||||
|
||||
## 완료 판정 원칙
|
||||
|
||||
- 코드 변경만으로 완료 처리하지 않는다.
|
||||
- 서버 배포 대상 기능은 CI/CD 성공과 실제 동작 확인을 요구한다.
|
||||
- API 기능은 단위 테스트 또는 통합 테스트와 함께 실제 HTTP 호출 결과를 확인한다.
|
||||
- DB 변경은 마이그레이션과 롤백 위험을 문서화한다.
|
||||
- 비밀값은 Gitea Secrets 또는 서버 환경변수로만 관리한다.
|
||||
|
||||
---
|
||||
|
||||
## ── 홈페이지 · SEO · UX ───────────────────────────
|
||||
|
||||
## WBS-UX-01 공개 홈페이지 UX/SEO 검증
|
||||
|
||||
목표: 공개 홈페이지가 검색 유입과 상담 전환에 맞는 구조인지 검증한다.
|
||||
|
||||
성공 기준:
|
||||
- 홈/블로그 목록/블로그 상세/상담 문의 페이지 200
|
||||
- 주요 페이지 title/description 존재
|
||||
- 모바일 viewport에서 주요 CTA가 보인다.
|
||||
- 상담 문의 제출 Playwright E2E가 통과한다.
|
||||
- 블로그 상세 SEO 메타 검증이 배포본 기준으로 통과한다.
|
||||
|
||||
Todo:
|
||||
- [x] 공개 페이지 Playwright smoke E2E 추가
|
||||
- [x] 상담 문의 제출 E2E 추가
|
||||
- [x] 블로그 상세 SEO 메타 검증 추가
|
||||
|
||||
검증 파일:
|
||||
- `tests/e2e/public-smoke.spec.ts`
|
||||
- `tests/e2e/blog-seo.spec.ts`
|
||||
- `tests/e2e/contact-submit.spec.ts`
|
||||
- `tests/e2e/inquiry-detail.spec.ts`
|
||||
|
||||
## WBS-UX-02 홈페이지 FAQ 섹션 (정적)
|
||||
|
||||
목표: 방문자가 상담 전 자주 묻는 질문에서 직접 답을 얻고 전환율을 높인다.
|
||||
|
||||
성공 기준:
|
||||
- 홈페이지에 4개 FAQ 아코디언 표시 (기장료, 양도세 상담, 무료 상담, 첫 상담 준비물)
|
||||
- 아코디언 열림/닫힘 동작
|
||||
- 모바일에서 가독성 확인
|
||||
|
||||
Todo:
|
||||
- [x] Index.cshtml에 FAQ 아코디언 섹션 추가 (최종 CTA 앞)
|
||||
- [x] site.css faq-accordion / faq-item / faq-question / faq-answer 스타일
|
||||
- [x] 배포 완료 (`12070b7`)
|
||||
- [ ] 배포 후 브라우저 아코디언 동작 확인
|
||||
|
||||
## WBS-UX-04 개인정보처리방침·이용약관 페이지
|
||||
|
||||
목표: 법적 의무를 충족하고 방문자 신뢰를 높이는 정책 페이지를 제공한다.
|
||||
|
||||
성공 기준:
|
||||
- `/taxbaik/privacy` 개인정보처리방침 페이지 정상 렌더링 (200)
|
||||
- `/taxbaik/terms` 이용약관 페이지 정상 렌더링 (200)
|
||||
- 푸터에 두 페이지 링크 표시
|
||||
- 개인정보처리방침: 수집 항목, 이용 목적, 보유 기간, 파기 방법, 책임자 정보 포함
|
||||
- 이용약관: 목적, 서비스 범위, 면책 조항, 저작권, 준거법 포함
|
||||
|
||||
Todo:
|
||||
- [x] Privacy.cshtml + Privacy.cshtml.cs (Razor Page)
|
||||
- [x] Terms.cshtml + Terms.cshtml.cs (Razor Page)
|
||||
- [x] _Footer.cshtml에 링크 이미 존재 확인
|
||||
- [ ] 배포 후 /taxbaik/privacy, /taxbaik/terms 접근 확인
|
||||
|
||||
## WBS-UX-03 FAQ 관리 (어드민 CRUD)
|
||||
|
||||
목표: 세무사가 관리자 화면에서 FAQ 항목을 직접 등록·수정·삭제·순서 조정한다.
|
||||
홈페이지 FAQ가 하드코딩에서 DB 기반으로 전환되어, 코드 수정 없이 운영 가능해진다.
|
||||
|
||||
설계 방향:
|
||||
- FAQ 항목: 질문(question), 답변(answer), 정렬 순서(sort_order), 활성화 여부(is_active)
|
||||
- 홈페이지는 is_active=TRUE 항목을 sort_order 오름차순으로 표시
|
||||
- 카테고리 태그(선택): "기장·세금신고", "부동산", "증여·상속", "기타" — 홈페이지에서 탭 필터 가능
|
||||
|
||||
성공 기준:
|
||||
- 관리자 `/taxbaik/admin/faqs` 목록/생성/수정/삭제/순서변경 동작
|
||||
- 홈페이지 FAQ 섹션이 DB에서 로드 (하드코딩 제거)
|
||||
- 비활성 항목은 홈페이지 미표시
|
||||
- sort_order 기준 정렬
|
||||
|
||||
DB 스키마:
|
||||
- `faqs` 테이블 (V007 마이그레이션)
|
||||
- id SERIAL PK
|
||||
- question VARCHAR(300) NOT NULL
|
||||
- answer TEXT NOT NULL
|
||||
- category VARCHAR(50) — 기장·세금신고, 부동산, 증여·상속, 기타
|
||||
- sort_order INT DEFAULT 0
|
||||
- is_active BOOLEAN DEFAULT TRUE
|
||||
- created_at TIMESTAMPTZ
|
||||
- updated_at TIMESTAMPTZ
|
||||
|
||||
Todo:
|
||||
- [x] V007__CreateFaqs.sql 마이그레이션 (기본 FAQ 4개 시드 포함)
|
||||
- [x] Faq 엔티티 (Domain)
|
||||
- [x] IFaqRepository 인터페이스 (Domain)
|
||||
- [x] FaqRepository 구현 (Infrastructure) — sort_order 정렬, CRUD
|
||||
- [x] FaqService 구현 (Application) — Categories 상수, 유효성 검사
|
||||
- [x] FaqList.razor 관리자 목록 (활성/비활성 상태 칩, 삭제 확인)
|
||||
- [x] FaqEdit.razor 관리자 등록/수정 (질문/답변/카테고리/순서/활성 토글)
|
||||
- [x] Index.cshtml FAQ 섹션 하드코딩 → DB 루프로 교체 (빈 DB에도 안전)
|
||||
- [x] IndexModel FaqService 주입, Task.WhenAll 병렬 로드
|
||||
- [x] MainLayout.razor FAQ 관리 메뉴 추가 (홈페이지 그룹 하위)
|
||||
- [ ] 배포 후 관리자에서 FAQ 추가 → 홈페이지 반영 확인
|
||||
|
||||
---
|
||||
|
||||
## ── 시즌별 마케팅 ───────────────────────────────
|
||||
|
||||
## WBS-MKT-01 시즌별 홈페이지 자동 전환
|
||||
|
||||
목표: 세무 신고 시즌마다 홈페이지 Hero·CTA·서비스 카드 순서가 자동 변경된다.
|
||||
|
||||
성공 기준:
|
||||
- 7개 시즌(vat-2nd, year-end-settlement, corporate-tax, income-tax, vat-1st, comprehensive-real-estate-tax, year-end-gift) 날짜 판정 정확
|
||||
- 시즌 중 Hero에 UrgencyBadge 표시
|
||||
- D-7일 이내 긴박감 메시지 표시
|
||||
- FocusService 기준 서비스 카드 순서 자동 정렬
|
||||
- 최종 CTA 시즌 문구 전환
|
||||
|
||||
Todo:
|
||||
- [x] TaxSeason / TaxSeasonCalendar 정의
|
||||
- [x] CurrentSeasonDto / SeasonalMarketingService 구현
|
||||
- [x] Index.cshtml Hero 시즌 분기 렌더링
|
||||
- [x] Index.cshtml 서비스 카드 cardOrder 정렬 로직
|
||||
- [x] Index.cshtml 최종 CTA 시즌 전환
|
||||
- [x] CLAUDE.md 섹션 13 세무 캘린더 하네스
|
||||
- [ ] 배포 후 시즌 날짜 경계값 수동 확인
|
||||
|
||||
## WBS-MKT-04 시즌 시뮬레이터 (어드민)
|
||||
|
||||
목표: 관리자가 날짜를 선택해 홈페이지 시즌 화면을 사전에 확인하고 콘텐츠 준비를 계획한다.
|
||||
|
||||
배경: 7개 시즌이 자동 전환되므로, 실제 날짜가 되기 전 미리 Hero 화면을 확인하는 도구가 필요하다.
|
||||
|
||||
성공 기준:
|
||||
- 관리자 `/taxbaik/admin/season-simulator` 접근 가능
|
||||
- 날짜 선택 시 해당 날짜의 Hero 섹션 미리보기 렌더링
|
||||
- 각 시즌 버튼 클릭으로 해당 시즌 첫날로 즉시 이동
|
||||
- 비시즌 날짜 선택 시 기본 Hero 미리보기 표시
|
||||
- 연간 시즌 타임라인 테이블 표시
|
||||
|
||||
Todo:
|
||||
- [x] SeasonSimulator.razor 어드민 페이지 구현
|
||||
- [x] 날짜 선택 → 실시간 Hero 미리보기
|
||||
- [x] 시즌 빠른 이동 버튼 (7개 시즌)
|
||||
- [x] 연간 타임라인 테이블 (활성/비활성 구분)
|
||||
- [x] MainLayout.razor 시즌 시뮬레이터 메뉴 추가 (홈페이지 그룹 하위)
|
||||
- [ ] 배포 후 관리자에서 시뮬레이터 동작 확인
|
||||
|
||||
## WBS-MKT-02 관리자 공지사항 (Announcement)
|
||||
|
||||
목표: 운영자가 홈페이지 최상단 배너를 등록·수정·삭제할 수 있다.
|
||||
|
||||
성공 기준:
|
||||
- 관리자 `/taxbaik/admin/announcements` 목록/생성/수정/삭제 동작
|
||||
- is_active=TRUE + 기간 조건(starts_at~ends_at)에 해당하는 공지만 홈페이지에 노출
|
||||
- 유형(info/banner/urgent) 별 색상 배지 표시
|
||||
- 홈페이지 최상단 announcement-bar 노출
|
||||
|
||||
Todo:
|
||||
- [x] V005__CreateAnnouncements.sql 마이그레이션
|
||||
- [x] Announcement 엔티티, IAnnouncementRepository, AnnouncementRepository
|
||||
- [x] AnnouncementService 구현
|
||||
- [x] AnnouncementList.razor, AnnouncementEdit.razor 관리자 화면
|
||||
- [x] Index.cshtml 공지사항 배너 렌더링
|
||||
- [x] MainLayout.razor 공지사항 메뉴 추가
|
||||
- [ ] 배포 후 공지 등록 → 홈 노출 확인
|
||||
|
||||
## WBS-MKT-03 블로그 시즌 연동
|
||||
|
||||
목표: 시즌 활성 중 홈페이지 블로그 섹션이 시즌 관련 글을 우선 노출한다.
|
||||
|
||||
배경: 세무 시즌에 맞는 콘텐츠를 전면에 배치해 상담 전환율과 SEO 체류시간을 높인다.
|
||||
|
||||
성공 기준:
|
||||
- 시즌 중: 해당 카테고리 글 최대 2개(이번 시즌 추천 배지) + 최신 글로 3개 채움
|
||||
- 평상시: 최신 글 3개 (기존 동작)
|
||||
- 시즌별 전체 글 보기 버튼 (`/taxbaik/blog?category=<slug>`)
|
||||
- 배너 헤더가 시즌명 표시
|
||||
|
||||
카테고리 → 시즌 슬러그 매핑:
|
||||
- `vat-2nd` / `vat-1st` → `vat`
|
||||
- `income-tax` → `income-tax`
|
||||
- `year-end-settlement` / `corporate-tax` → `business-tax`
|
||||
- `comprehensive-real-estate-tax` → `real-estate-tax`
|
||||
- `year-end-gift` → `family-asset`
|
||||
|
||||
Todo:
|
||||
- [x] TaxSeason.RelatedCategorySlug 추가
|
||||
- [x] TaxSeasonCalendar 각 시즌에 카테고리 슬러그 매핑
|
||||
- [x] CurrentSeasonDto.RelatedCategorySlug 추가
|
||||
- [x] SeasonalMarketingService에 RelatedCategorySlug 전달
|
||||
- [x] IBlogPostRepository.GetByCategorySlugAsync 추가
|
||||
- [x] BlogPostRepository.GetByCategorySlugAsync 구현
|
||||
- [x] BlogService.GetSeasonalPostsAsync 추가
|
||||
- [x] IndexModel SeasonalPosts/RecentPosts 분리 로드
|
||||
- [x] Index.cshtml 블로그 섹션 시즌 분기 렌더링
|
||||
- [x] site.css 블로그 시즌 강조 스타일 추가
|
||||
- [ ] 배포 후 시즌 활성 날짜에 블로그 카드 "이번 시즌 추천" 배지 확인
|
||||
|
||||
---
|
||||
|
||||
## ── 운영 인프라 ─────────────────────────────────
|
||||
|
||||
## WBS-OPS-01 배포 검증 게이트 고도화
|
||||
|
||||
목표: curl/API만이 아니라 실제 브라우저 검증까지 통과해야 배포를 성공으로 본다.
|
||||
|
||||
성공 기준:
|
||||
- `dotnet build TaxBaik.sln -c Release` 경고 0, 오류 0
|
||||
- `dotnet test TaxBaik.sln -c Release --no-build` 전체 통과
|
||||
- CI 배포 후 Playwright가 `/taxbaik/admin/login`에서 실제 로그인 수행
|
||||
- 로그인 후 `/taxbaik/admin/dashboard` 도달
|
||||
- 브라우저 console error 및 page error 0개
|
||||
|
||||
Todo:
|
||||
- [x] Playwright Test 프로젝트 추가
|
||||
- [x] 관리자 로그인 E2E 추가
|
||||
- [x] CI 배포 후 Playwright 실행 단계 추가
|
||||
- [x] Playwright가 발견한 Blazor DI 결함 수정
|
||||
- [ ] CI run에서 Playwright 전체 통과 확인
|
||||
- [ ] 배포 검증에 블로그 상세/문의/비밀번호 변경 성공 기준 반영 확인
|
||||
|
||||
## WBS-OPS-02 배포 502 / Nginx 유지보수 페이지
|
||||
|
||||
목표: CI 배포 중 502 Bad Gateway 대신 한국어 유지보수 페이지를 제공한다.
|
||||
|
||||
성공 기준:
|
||||
- Nginx error_page 502/503 → maintenance.html 직접 서빙
|
||||
- 배포 중 방문자는 유지보수 페이지(15초 자동 새로고침)를 본다.
|
||||
- 배포 완료 후 정상 서비스 복구
|
||||
|
||||
Todo:
|
||||
- [x] maintenance.html 작성
|
||||
- [x] Nginx error_page 502 503 @taxbaik_maintenance 설정
|
||||
- [x] 서버 측 헬스 루프 (40회×3초) 단일 SSH 연결로 처리
|
||||
- [x] CI 배포 단계 헬스 체크 고도화
|
||||
|
||||
## WBS-OPS-03 관리자 401 수정
|
||||
|
||||
목표: 직접 URL 접근 시 관리자 Blazor 페이지가 401로 차단되지 않는다.
|
||||
|
||||
성공 기준:
|
||||
- `/taxbaik/admin/announcements` 등 직접 접근 시 Blazor Shell 200 응답
|
||||
- 미인증 사용자는 로그인 페이지로 리다이렉트
|
||||
|
||||
Todo:
|
||||
- [x] MapRazorComponents().AllowAnonymous() 적용
|
||||
- [x] AuthorizeRouteView → RedirectToLogin 인증 흐름 확인
|
||||
|
||||
---
|
||||
|
||||
## ── 인증 · 관리자 ─────────────────────────────────
|
||||
|
||||
## WBS-AUTH-01 인증/비밀번호 운영 안정화
|
||||
|
||||
목표: DB 직접 수정 대신 API로 관리자 인증 운영 작업을 수행한다.
|
||||
|
||||
성공 기준:
|
||||
- 비밀번호 변경 API가 현재 비밀번호를 요구한다.
|
||||
- 비밀번호 재설정 API는 운영 secret 없이는 동작하지 않는다.
|
||||
- 실패 응답은 민감 정보를 노출하지 않는다.
|
||||
|
||||
Todo:
|
||||
- [x] 로그인 API 검증
|
||||
- [x] 비밀번호 변경 API 추가
|
||||
- [x] 재설정 API 추가
|
||||
- [x] 관리자 UI에 비밀번호 변경 화면 추가
|
||||
- [x] 비밀번호 변경 Playwright E2E 추가
|
||||
|
||||
## WBS-ADMIN-01 관리자 Blazor 안정화
|
||||
|
||||
목표: 관리자 화면을 일반 웹페이지처럼 명시적 사용자 액션에만 갱신하고, circuit 예외를 배포 전 차단한다.
|
||||
|
||||
성공 기준:
|
||||
- 관리자 주요 메뉴 대시보드/블로그/문의/설정/공지사항 circuit error 0개
|
||||
- 저장/삭제/상태 변경 액션은 성공/실패 메시지를 표시한다.
|
||||
|
||||
Todo:
|
||||
- [x] 중복 `/admin` 라우트 제거
|
||||
- [x] MudBlazor DI 타입 오류 수정
|
||||
- [x] 관리자 메뉴 smoke E2E 추가
|
||||
- [x] 설정 저장 TODO를 실제 DB 기반 기능으로 전환
|
||||
|
||||
---
|
||||
|
||||
## ── 고객지원 백오피스 (CRM) ──────────────────────
|
||||
|
||||
> **배경**: 세무사 사무실에서 고객 정보와 상담 이력이 파편화(메모장·카톡·기억)되면 마감 누락, 서비스 연속성 단절, 재계약 기회 손실이 발생한다.
|
||||
> 30년 경력 세무사가 혼자 또는 소수 인원으로 운영할 때 가장 먼저 필요한 것은 고객 카드와 상담 이력이다.
|
||||
|
||||
## WBS-CRM-01 고객 카드 (Client Card) — Phase 1
|
||||
|
||||
목표: 고객별 기본 정보·서비스 유형·상태를 한 화면에서 관리한다.
|
||||
|
||||
성공 기준:
|
||||
- 관리자 `/taxbaik/admin/clients` 목록/검색/생성/수정/삭제 동작
|
||||
- 고객 카드: 이름, 회사명, 연락처, 이메일, 서비스 유형, 세금 유형, 상태, 유입 경로, 메모
|
||||
- 상태 필터(활성/비활성)로 목록 조회
|
||||
- 고객 저장 시 updated_at 자동 갱신
|
||||
|
||||
DB 스키마:
|
||||
- `clients` 테이블 (V006 마이그레이션)
|
||||
- 컬럼: id, name, company_name, phone, email, service_type, tax_type, status, source, memo, created_at, updated_at
|
||||
|
||||
Todo:
|
||||
- [x] V006__CreateClients.sql 마이그레이션
|
||||
- [x] Client 엔티티 (Domain)
|
||||
- [x] IClientRepository 인터페이스 (Domain) — GetPagedAsync 검색+상태 필터
|
||||
- [x] ClientRepository 구현 (Infrastructure) — ILIKE 검색, 페이징
|
||||
- [x] ClientService 구현 (Application) — ServiceTypes/TaxTypes/Sources 상수
|
||||
- [x] ClientList.razor 관리자 목록 화면 — 검색바, 상태 필터, 페이징
|
||||
- [x] ClientEdit.razor 관리자 등록/수정 화면 — 기본/세무/관리 섹션
|
||||
- [x] MainLayout.razor 고객 관리 NavGroup 추가
|
||||
- [ ] 배포 후 고객 등록 → 목록 조회 확인
|
||||
|
||||
## WBS-CRM-02 상담 이력 (Consultation Log) — Phase 1
|
||||
|
||||
목표: 고객별 상담 일자·내용·결과·수수료를 기록해 "이 고객 지난번에 뭐 상담했더라?"를 해결한다.
|
||||
|
||||
성공 기준:
|
||||
- 고객 상세에서 상담 이력 목록/추가/삭제 동작
|
||||
- 상담 이력 필드: 날짜, 서비스 유형, 상담 요약, 결과(계약/보류/거절/완료), 수수료
|
||||
- 이력 없는 고객은 빈 목록 표시
|
||||
|
||||
DB 스키마:
|
||||
- `consultations` 테이블 (V008 마이그레이션)
|
||||
- 컬럼: id, client_id(FK), consultation_date, service_type, summary, result, fee, created_at
|
||||
|
||||
Todo:
|
||||
- [x] V008__CreateConsultations.sql 마이그레이션
|
||||
- [x] Consultation 엔티티 (Domain)
|
||||
- [x] IConsultationRepository 인터페이스 (Domain)
|
||||
- [x] ConsultationRepository 구현 (Infrastructure)
|
||||
- [x] ConsultationService 구현 (Application)
|
||||
- [x] ClientDetail.razor (고객 상세 + 상담 이력 추가/삭제)
|
||||
- [x] DI 등록 (Infrastructure + Application)
|
||||
- [ ] 배포 후 고객 상세에서 상담 이력 추가 확인
|
||||
|
||||
## WBS-CRM-03 문의 → 고객 전환 — Phase 1
|
||||
|
||||
목표: 홈페이지 문의 접수 건을 클릭 한 번으로 고객 카드로 등록한다.
|
||||
|
||||
성공 기준:
|
||||
- 문의 상세에 "고객으로 등록" 버튼 표시
|
||||
- 버튼 클릭 시 고객 카드 자동 생성 후 연결
|
||||
- 이미 연결된 고객이 있으면 버튼 대신 고객 카드 링크 표시
|
||||
- inquiries 테이블에 client_id, admin_memo, updated_at 컬럼 추가
|
||||
|
||||
Todo:
|
||||
- [x] V009__AddClientIdToInquiries.sql 마이그레이션
|
||||
- [x] Inquiry 엔티티 client_id, admin_memo, updated_at 추가
|
||||
- [x] IInquiryRepository.LinkClientAsync, UpdateAdminMemoAsync 추가
|
||||
- [x] InquiryRepository 구현
|
||||
- [x] InquiryService.LinkClientAsync, UpdateAdminMemoAsync 추가
|
||||
- [x] ClientService.CreateFromInquiryAsync 추가
|
||||
- [x] InquiryDetail.razor "고객으로 등록" 버튼 + 담당자 메모 추가
|
||||
- [ ] 배포 후 문의 → 고객 전환 흐름 확인
|
||||
|
||||
---
|
||||
|
||||
## ── 고객지원 백오피스 Phase 2 ──────────────────────
|
||||
|
||||
## WBS-CRM-04 신고 일정 캘린더 — Phase 2
|
||||
|
||||
목표: 고객별 신고 예정일과 마감일을 추적해 가산세 리스크를 방지한다.
|
||||
|
||||
성공 기준:
|
||||
- 관리자에서 고객별 세금 신고 일정 등록/수정/완료 처리
|
||||
- D-Day 표시 (D-7일 이내 강조)
|
||||
- 이번 달 마감 목록을 대시보드 위젯으로 표시
|
||||
|
||||
DB 스키마:
|
||||
- `tax_filings` 테이블 (V010 마이그레이션)
|
||||
- 컬럼: id, client_id(FK), filing_type, due_date, status(pending/filed/overdue), memo
|
||||
|
||||
Todo:
|
||||
- [x] V010__CreateTaxFilings.sql
|
||||
- [x] TaxFiling 엔티티 (Domain)
|
||||
- [x] ITaxFilingRepository, TaxFilingRepository 구현
|
||||
- [x] TaxFilingService 구현 (Application)
|
||||
- [x] TaxFilingList.razor (관리자 신고 일정 화면 + 상태별 탭)
|
||||
- [x] FilingTable.razor (D-Day 강조, 완료 처리, 삭제)
|
||||
- [x] Dashboard.razor에 30일 이내 마감 위젯 추가
|
||||
- [x] MainLayout.razor 신고 일정 메뉴 추가
|
||||
- [x] DI 등록
|
||||
- [ ] 배포 후 신고 일정 등록 → D-Day 표시 확인
|
||||
|
||||
## WBS-CRM-05 문의 접수 현황 강화 — Phase 2
|
||||
|
||||
목표: 문의 상태를 세분화하고 담당자 메모를 기록해 처리 흐름을 추적한다.
|
||||
|
||||
성공 기준:
|
||||
- 문의 상태: 신규/상담중/계약완료/거절/종결 5단계
|
||||
- 목록에서 상태 탭 필터로 빠른 분류
|
||||
- 상태 변경 시 updated_at 자동 기록
|
||||
|
||||
Todo:
|
||||
- [x] V011__ExtendInquiryStatus.sql 마이그레이션 (contacted→consulting, completed→closed, admin_memo/updated_at 추가)
|
||||
- [x] InquiryStatus enum 5단계 확장
|
||||
- [x] InquiryStatusMapper 5단계 레이블 + TryParse 업데이트
|
||||
- [x] InquiryList.razor 5단계 탭 (신규/상담중/계약완료/거절/종결)
|
||||
- [x] InquiryDetail.razor 5단계 상태 버튼 + 색상 구분
|
||||
- [x] Dashboard.razor 상태 레이블 5단계 반영
|
||||
|
||||
---
|
||||
|
||||
## ── 고객지원 백오피스 Phase 3 ──────────────────────
|
||||
|
||||
## WBS-CRM-06 텔레그램 자동 리포트 — Phase 3
|
||||
|
||||
목표: 세무사에게 일/주 단위 신규 문의·처리 현황·마감 임박 건을 텔레그램으로 전송한다.
|
||||
|
||||
성공 기준:
|
||||
- 매일 오전 9시 신규 문의 수, 처리 대기 수 자동 전송
|
||||
- 매주 월요일 주간 리포트 (신규 고객, 이번 주 마감 신고 건)
|
||||
- 텔레그램 전송 실패 시 로그만 남기고 앱 정상 운영 유지
|
||||
|
||||
Todo:
|
||||
- [x] BackgroundService 또는 Hangfire 기반 스케줄러 추가
|
||||
- [x] 일간/주간 리포트 메시지 템플릿
|
||||
- [x] TelegramNotificationService에 리포트 메서드 추가
|
||||
|
||||
## WBS-CRM-07 고객 포털 (읽기 전용) — Phase 3
|
||||
|
||||
목표: 기장 고객이 본인 신고 현황과 중요 알림을 직접 확인한다.
|
||||
|
||||
성공 기준:
|
||||
- 고객 전용 URL + 인증(소셜 로그인 또는 링크 토큰)
|
||||
- 본인 신고 일정, 상담 요약(세무사 허용 항목만) 조회
|
||||
- 개인정보 열람 범위는 세무사가 허용한 항목만
|
||||
|
||||
Todo:
|
||||
- [x] 고객 포털 설계 (인증 방식 결정 — WBS-CRM-08 선행)
|
||||
- [x] 고객 전용 Razor Pages 추가
|
||||
- [x] 세무사 허용 권한 설정 UI
|
||||
|
||||
## WBS-CRM-08 고객 회원가입 · 소셜 로그인 — Phase 3
|
||||
|
||||
목표: 고객 포털 접근을 위한 회원가입과 소셜 로그인을 제공한다.
|
||||
가입 마찰을 최소화해 상담 접수 → 고객 포털 전환율을 높인다.
|
||||
|
||||
설계 방향:
|
||||
- 가입 입력 최소화: 이름 + 연락처(또는 이메일) 2필드면 충분
|
||||
- 소셜 로그인 우선: 비밀번호 없이 바로 가입
|
||||
- 기본 계정(이메일/비밀번호) 옵션도 제공 (소셜 없는 사용자 대비)
|
||||
- 고객 포털 전용 인증 — 관리자(admin_users)와 완전히 분리
|
||||
|
||||
지원 소셜 로그인:
|
||||
- 네이버 (Naver OAuth 2.0) — 국내 주요 채널
|
||||
- 카카오 (Kakao Login) — 기존 카카오 채널 연계
|
||||
- 구글 (Google OAuth 2.0) — 해외·젊은 고객층
|
||||
|
||||
성공 기준:
|
||||
- 소셜 로그인 3종 모두 동작 (네이버·카카오·구글)
|
||||
- 이메일/비밀번호 기본 계정 가입 + 로그인 동작
|
||||
- 가입 폼: 이름·연락처 2필드만 요구 (소셜 프로필에서 자동 채우기)
|
||||
- 로그인 후 고객 포털 (`/taxbaik/portal`) 접근
|
||||
- 고객 계정이 백오피스 clients 테이블 레코드와 연결
|
||||
- 회원 계정 미인증 상태에서 포털 접근 시 로그인 페이지 리다이렉트
|
||||
|
||||
DB 스키마:
|
||||
- `portal_users` 테이블 (V011 마이그레이션)
|
||||
- id, client_id(FK, nullable), email, name, phone, provider(naver/kakao/google/local), provider_id, password_hash(nullable), created_at
|
||||
- 소셜 로그인 provider_id는 각 플랫폼 식별자
|
||||
|
||||
기술 결정:
|
||||
- ASP.NET Core OAuth Middleware (Microsoft.AspNetCore.Authentication.OAuth)
|
||||
- 네이버: 커스텀 OAuth handler (공식 패키지 없음, 직접 구현)
|
||||
- 카카오: AspNet.Security.OAuth.Kakao 패키지
|
||||
- 구글: Microsoft.AspNetCore.Authentication.Google 패키지
|
||||
- 고객 포털 세션: HttpOnly Cookie 기반 (JWT localStorage와 분리)
|
||||
|
||||
환경 변수 필요 (Gitea Secrets 추가):
|
||||
- `NAVER_CLIENT_ID` / `NAVER_CLIENT_SECRET`
|
||||
- `KAKAO_CLIENT_ID` / `KAKAO_CLIENT_SECRET`
|
||||
- `GOOGLE_CLIENT_ID` / `GOOGLE_CLIENT_SECRET`
|
||||
|
||||
Todo:
|
||||
- [x] WBS-CRM-07 고객 포털 기본 구조 완성 (선행)
|
||||
- [x] OAuth 앱 등록 (네이버·카카오·구글 개발자 콘솔)
|
||||
- [x] V011__CreatePortalUsers.sql 마이그레이션 (실제 V016__CreatePortalUsers.sql로 대체됨)
|
||||
- [x] PortalUser 엔티티 / IPortalUserRepository / PortalUserRepository
|
||||
- [x] 네이버 OAuth Handler 구현
|
||||
- [x] 카카오·구글 패키지 추가 및 설정
|
||||
- [x] 기본 계정 회원가입 폼 (`/taxbaik/portal/register`)
|
||||
- [x] 소셜 로그인 콜백 처리 → portal_users 자동 생성
|
||||
- [x] 신규 가입 시 clients 테이블 연결 또는 신규 생성
|
||||
- [x] 포털 로그인 페이지 (`/taxbaik/portal/login`) — 소셜 버튼 + 이메일 폼
|
||||
- [ ] Gitea Secrets에 OAuth 키 추가
|
||||
- [ ] 배포 후 소셜 로그인 3종 E2E 테스트
|
||||
|
||||
---
|
||||
|
||||
## ── 유지보수성 ─────────────────────────────────
|
||||
|
||||
## WBS-MAINT-01 유지보수성/파편화 축소
|
||||
|
||||
목표: 문서와 실제 구조의 불일치를 줄이고 단일 앱 운영 기준을 유지한다.
|
||||
|
||||
Todo:
|
||||
- [x] README 테스트/배포 섹션 갱신
|
||||
- [x] CLAUDE.md E2E 기준 갱신
|
||||
- [x] 오래된 최종 보고 문서의 허위 완료 표현 정정
|
||||
- [x] CLAUDE.md 섹션 13 시즌별 마케팅 하네스 추가
|
||||
|
||||
---
|
||||
|
||||
### 현재 검증 메모
|
||||
|
||||
- `dotnet build TaxBaik.sln` 성공 (2026-06-27 기준, 경고 0 오류 0)
|
||||
- 최종 배포 커밋: `9c96f15` (FAQ 관리 기능)
|
||||
- WBS-MKT-01/02/03/04 구현 완료, 배포 후 시각 검증 필요
|
||||
- WBS-UX-03/04 구현 완료
|
||||
- WBS-CRM-01/02/03/04/05 구현 완료 (배포 후 검증 필요)
|
||||
- WBS-CRM-06/07/08 (텔레그램·포털·소셜 로그인) Phase 3 미착수
|
||||
|
||||
---
|
||||
|
||||
## ── 홈페이지 · 어드민 · 포털 프리미엄 UX/UI 개편 (2026-06-30) ──────────────────
|
||||
|
||||
## WBS-UX-05 홈페이지 프리미엄 UI 및 마이크로 인터랙션
|
||||
|
||||
목표: 홈페이지 디자인을 극도로 모던하고 신뢰성 있는 프리미엄 스타일로 전면 개편한다.
|
||||
|
||||
성공 기준:
|
||||
- Hero 섹션에 유려한 배경 그라데이션 및 부드러운 CSS 애니메이션 효과 적용
|
||||
- 서비스 카드에 섀도우 및 보더 트랜지션, 골드/그린 그라데이션 호버 이펙트 추가
|
||||
- 신뢰도 스트립 카드에 입체감 및 돋보이는 레이아웃 설계
|
||||
- Noto Sans KR 외에 Outfit/Inter 등의 보조 영문 폰트 결합으로 타이포그래피 고급화
|
||||
|
||||
Todo:
|
||||
- [x] `site.css` 내 Hero 섹션 그라데이션 및 CSS 애니메이션 보강
|
||||
- [x] 서비스 카드 및 신뢰도 스트립 컴포넌트 프리미엄 스타일로 개편
|
||||
- [x] 홈페이지 폰트 스택 확장 및 메인 레이아웃 적용
|
||||
|
||||
## WBS-PORTAL-01 고객 포털 UI/UX 고도화 및 글래스모피즘
|
||||
|
||||
목표: 고객 마이 포털 화면을 미려하고 현대적인 글래스모피즘 디자인으로 개편하여 이용 가치를 극대화한다.
|
||||
|
||||
성공 기준:
|
||||
- 포털 메인 대시보드 카드를 Glassmorphism 스타일(blur, semi-transparent border)로 변경
|
||||
- 세무 신고 현황 테이블 및 상담 이력 타임라인 컴포넌트의 모던 디자인화
|
||||
|
||||
Todo:
|
||||
- [x] `site.css` 내 포털 전용 모던 글래스모피즘 클래스군 추가
|
||||
- [x] `Portal/Index.cshtml` 레이아웃 및 컴포넌트 UI 고도화
|
||||
|
||||
## WBS-MAINT-02 코드 품질 및 경고 결함 차단
|
||||
|
||||
목표: 빌드 컴파일 타임 경고(Warnings)를 0으로 유지하여 미래 코드 결함을 방지한다.
|
||||
|
||||
성공 기준:
|
||||
- `dotnet build` 수행 시 경고 0개 달성
|
||||
|
||||
Todo:
|
||||
- [x] `CustomAuthenticationStateProvider.cs` Nullable 경고 수정
|
||||
- [x] `Dashboard.razor` 미사용 변수 제거 및 UI 연계 바인딩 처리
|
||||
|
||||
@@ -0,0 +1,281 @@
|
||||
# 서버 설정 가이드
|
||||
|
||||
## 전제
|
||||
- 서버: `178.104.200.7` (Ubuntu 26.04)
|
||||
- 접속: `ssh kjh2064@178.104.200.7`
|
||||
|
||||
---
|
||||
|
||||
## Step 1: PostgreSQL 데이터베이스 생성 및 마이그레이션
|
||||
|
||||
서버에 SSH로 접속한 후 다음을 실행하세요:
|
||||
|
||||
```bash
|
||||
# PostgreSQL에 접속 (로컬 unix socket, sudo 사용)
|
||||
sudo -i -u postgres
|
||||
|
||||
# 그 다음 psql 프롬프트에서:
|
||||
psql
|
||||
|
||||
# SQL 명령어 실행:
|
||||
CREATE DATABASE taxbaikdb;
|
||||
CREATE USER taxbaik WITH ENCRYPTED PASSWORD 'your_secure_password_here';
|
||||
GRANT ALL PRIVILEGES ON DATABASE taxbaikdb TO taxbaik;
|
||||
\q
|
||||
exit
|
||||
|
||||
# 이제 taxbaik 사용자로 마이그레이션 실행
|
||||
psql -U taxbaik -d taxbaikdb -h localhost
|
||||
```
|
||||
|
||||
그러면 `taxbaik=#` 프롬프트가 나옵니다. 다음 SQL을 차례로 실행:
|
||||
|
||||
### V001__InitialSchema.sql 내용 (프롬프트에 복사):
|
||||
|
||||
```sql
|
||||
CREATE TABLE categories (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
slug VARCHAR(100) NOT NULL UNIQUE,
|
||||
sort_order INT NOT NULL DEFAULT 0
|
||||
);
|
||||
|
||||
CREATE TABLE admin_users (
|
||||
id SERIAL PRIMARY KEY,
|
||||
username VARCHAR(100) NOT NULL UNIQUE,
|
||||
password_hash VARCHAR(500) NOT NULL,
|
||||
last_login_at TIMESTAMPTZ,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE TABLE blog_posts (
|
||||
id SERIAL PRIMARY KEY,
|
||||
title VARCHAR(300) NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
slug VARCHAR(300) NOT NULL UNIQUE,
|
||||
category_id INT REFERENCES categories(id) ON DELETE SET NULL,
|
||||
tags TEXT,
|
||||
author_id INT REFERENCES admin_users(id) ON DELETE SET NULL,
|
||||
published_at TIMESTAMPTZ,
|
||||
view_count INT NOT NULL DEFAULT 0,
|
||||
seo_title VARCHAR(300),
|
||||
seo_description VARCHAR(500),
|
||||
thumbnail_url VARCHAR(500),
|
||||
is_published BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_blog_slug ON blog_posts(slug);
|
||||
CREATE INDEX idx_blog_published ON blog_posts(is_published, published_at DESC);
|
||||
CREATE INDEX idx_blog_category ON blog_posts(category_id);
|
||||
|
||||
CREATE TABLE inquiries (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
phone VARCHAR(20) NOT NULL,
|
||||
email VARCHAR(200),
|
||||
service_type VARCHAR(100) NOT NULL,
|
||||
message TEXT NOT NULL,
|
||||
status VARCHAR(50) NOT NULL DEFAULT 'new',
|
||||
ip_address VARCHAR(50),
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_inquiry_status ON inquiries(status);
|
||||
CREATE INDEX idx_inquiry_created ON inquiries(created_at DESC);
|
||||
|
||||
CREATE TABLE site_settings (
|
||||
key VARCHAR(200) PRIMARY KEY,
|
||||
value TEXT NOT NULL,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
INSERT INTO categories (name, slug, sort_order) VALUES
|
||||
('사업자 세무', 'business-tax', 1),
|
||||
('부동산 세금', 'real-estate-tax', 2),
|
||||
('종합소득세', 'income-tax', 3),
|
||||
('부가가치세', 'vat', 4),
|
||||
('가족자산·증여', 'family-asset', 5);
|
||||
|
||||
INSERT INTO site_settings (key, value) VALUES
|
||||
('site.title', '백원숙 세무회계 | 성북구 세무사'),
|
||||
('site.description', '사업자 세무, 부동산 양도세·증여세, 종합소득세 상담. 성북구 백원숙 세무사.'),
|
||||
('kakao.channel.url', ''),
|
||||
('phone.main', ''),
|
||||
('consultation.fee.text','상담료 7만~20만 원, 계약 체결 시 일부 차감');
|
||||
```
|
||||
|
||||
### 확인:
|
||||
```sql
|
||||
\dt
|
||||
-- 6개 테이블 출력 (categories, admin_users, blog_posts, inquiries, site_settings, 그리고 schema_migrations)
|
||||
|
||||
SELECT * FROM categories;
|
||||
-- 5개 카테고리 확인
|
||||
|
||||
\q
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 2: systemd 서비스 파일 설치
|
||||
|
||||
로컬 컴퓨터에서 다음 파일을 서버에 복사:
|
||||
|
||||
```bash
|
||||
# 로컬에서:
|
||||
scp deploy/taxbaik.service kjh2064@178.104.200.7:~/
|
||||
```
|
||||
|
||||
서버에서:
|
||||
```bash
|
||||
# 파일 복사
|
||||
sudo cp ~/taxbaik.service /etc/systemd/system/
|
||||
|
||||
# 환경 변수 추가 (DB 연결 문자열)
|
||||
sudo nano /etc/systemd/system/taxbaik.service
|
||||
# 아래 줄을 [Service] 섹션에서 주석 해제:
|
||||
# Environment=ConnectionStrings__Default=Host=localhost;Database=taxbaikdb;Username=taxbaik;Password=your_password
|
||||
|
||||
# systemd 재로드
|
||||
sudo systemctl daemon-reload
|
||||
|
||||
# 서비스 활성화 (부팅 시 자동 시작)
|
||||
sudo systemctl enable taxbaik
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Nginx 설정 추가
|
||||
|
||||
로컬에서 `deploy/nginx-taxbaik-locations.conf` 내용을 복사하여, 서버에서:
|
||||
|
||||
```bash
|
||||
# 현재 Nginx 설정 확인
|
||||
cat /etc/nginx/sites-available/default
|
||||
|
||||
# 또는 기존 Nginx 설정 파일 확인
|
||||
ls -la /etc/nginx/sites-available/
|
||||
```
|
||||
|
||||
기존 설정 파일의 `http` 또는 `server` 블록에 다음을 **추가**:
|
||||
|
||||
```nginx
|
||||
location /taxbaik {
|
||||
proxy_pass http://127.0.0.1:5001;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection $connection_upgrade;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_read_timeout 120s;
|
||||
}
|
||||
```
|
||||
|
||||
설정 검증 및 재로드:
|
||||
```bash
|
||||
sudo nginx -t
|
||||
sudo systemctl reload nginx
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 4: Gitea 저장소 Secrets 추가
|
||||
|
||||
1. Gitea 웹UI (`http://178.104.200.7/`) 접속
|
||||
2. `kjh2064/taxbaik` 저장소 이동
|
||||
3. **Settings** → **Secrets** → **Add Secret**
|
||||
|
||||
다음 3개 secrets 추가:
|
||||
|
||||
| Secret 이름 | 값 |
|
||||
|-----------|-----|
|
||||
| `DEPLOY_USER` | `kjh2064` |
|
||||
| `DEPLOY_HOST` | `178.104.200.7` |
|
||||
| `DEPLOY_SSH_KEY` | SSH 개인키 (매우 민감!) |
|
||||
|
||||
SSH 개인키는:
|
||||
```bash
|
||||
# 로컬에서 (.ssh/id_rsa 또는 현재 사용 중인 키)
|
||||
cat ~/.ssh/id_rsa
|
||||
# 전체 내용 복사
|
||||
```
|
||||
|
||||
Gitea Secret 창에 전체 붙여넣기.
|
||||
|
||||
---
|
||||
|
||||
## Step 5: 배포 디렉토리 생성
|
||||
|
||||
서버에서:
|
||||
```bash
|
||||
mkdir -p ~/deployments
|
||||
mkdir -p ~/taxbaik_active
|
||||
# 권한 확인
|
||||
ls -la ~/ | grep taxbaik
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 6: 초기 배포 테스트
|
||||
|
||||
로컬에서:
|
||||
```bash
|
||||
# 솔루션 빌드
|
||||
dotnet build TaxBaik.sln -c Release
|
||||
|
||||
# 배포는 Gitea Actions가 처리
|
||||
# 수동 publish/rsync 절차는 사용하지 않음
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 7: 확인 체크리스트
|
||||
|
||||
```bash
|
||||
# 로컬에서 또는 서버에서:
|
||||
|
||||
# 1. DB 확인
|
||||
psql -U taxbaik -d taxbaikdb -c "\dt"
|
||||
# 6개 테이블 출력
|
||||
|
||||
# 2. 서비스 상태
|
||||
sudo systemctl status taxbaik
|
||||
|
||||
# 3. Nginx 로그 확인
|
||||
sudo tail -f /var/log/nginx/error.log
|
||||
|
||||
# 4. 앱 로그 확인
|
||||
journalctl -u taxbaik -n 50
|
||||
# 5. 엔드포인트 테스트
|
||||
curl -v http://127.0.0.1:5001/health
|
||||
curl -v http://127.0.0.1/taxbaik
|
||||
|
||||
# 6. 문의 폼 E2E 테스트
|
||||
curl -X POST http://178.104.200.7/taxbaik/contact \
|
||||
-d "name=테스트&phone=010-1234-5678&service_type=기장&message=상담요청"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 문제 해결
|
||||
|
||||
| 증상 | 해결 |
|
||||
|------|------|
|
||||
| "Connection refused" | systemd 서비스 시작 안 됨: `sudo systemctl start taxbaik` |
|
||||
| "Permission denied" | systemd 서비스 파일 권한 확인: `sudo chmod 644 /etc/systemd/system/taxbaik.service` |
|
||||
| Nginx 404 | 설정 재로드: `sudo nginx -t && sudo systemctl reload nginx` |
|
||||
| DB 연결 오류 | 환경 변수 확인: `systemctl cat taxbaik \| grep ConnectionStrings` |
|
||||
|
||||
---
|
||||
|
||||
## 다음 단계
|
||||
|
||||
서버 설정 완료 후:
|
||||
- [ ] Git 저장소에 push: `git push -u origin main`
|
||||
- [ ] 초기 블로그 포스트 5개 작성 (W6.2)
|
||||
- [ ] 사이트 설정 입력 (전화번호, KakaoTalk URL)
|
||||
- [ ] Lighthouse 감사 실행
|
||||
Reference in New Issue
Block a user