This commit is contained in:
@@ -1 +0,0 @@
|
||||
{"sessionId":"c3cb93c0-7adf-4d3a-817d-6c01e0e0f09f","pid":26816,"acquiredAt":1782481349474}
|
||||
@@ -42,7 +42,7 @@ jobs:
|
||||
echo "Built: $BUILD_TIME" >> ./publish/wwwroot/version.txt
|
||||
echo "✓ Version: $COMMIT_HASH"
|
||||
|
||||
- name: Deploy (통합 Web)
|
||||
- name: Deploy (CI only, 통합 Web)
|
||||
run: |
|
||||
set -e
|
||||
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
||||
@@ -55,7 +55,7 @@ jobs:
|
||||
ln -sfn "$DEPLOY_DIR" "$DEPLOY_HOME/taxbaik_active"
|
||||
echo "✓ Deployed to $DEPLOY_DIR"
|
||||
|
||||
# systemd가 자동재시작하도록 프로세스 종료 (sudo 불필요)
|
||||
# systemd가 새 아티팩트를 다시 읽도록 프로세스 종료
|
||||
echo "=== Restarting service ==="
|
||||
pkill -f "TaxBaik.Web.dll" || true
|
||||
sleep 3
|
||||
@@ -64,10 +64,16 @@ jobs:
|
||||
- name: Verify deployment
|
||||
run: |
|
||||
sleep 5
|
||||
STATUS=$(curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:5001/taxbaik/admin/login || echo "000")
|
||||
echo "HTTP Status: $STATUS"
|
||||
if [ "$STATUS" = "200" ] || [ "$STATUS" = "301" ] || [ "$STATUS" = "302" ]; then
|
||||
HOME_STATUS=$(curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:5001/taxbaik/ || echo "000")
|
||||
LOGIN_STATUS=$(curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:5001/taxbaik/admin/login || echo "000")
|
||||
AUTH_BODY=$(curl -s -X POST http://127.0.0.1:5001/taxbaik/api/auth/login \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"username":"admin","password":"admin123"}' || echo "")
|
||||
echo "Home Status: $HOME_STATUS"
|
||||
echo "Login Status: $LOGIN_STATUS"
|
||||
echo "Auth Body: $AUTH_BODY"
|
||||
if [ "$HOME_STATUS" = "200" ] && [ "$LOGIN_STATUS" = "200" ] && echo "$AUTH_BODY" | grep -q '"token"'; then
|
||||
echo "✓ Service is running"
|
||||
else
|
||||
echo "⚠ Service may not be running (status: $STATUS)"
|
||||
echo "⚠ Service may not be running (home: $HOME_STATUS, login: $LOGIN_STATUS, auth: $AUTH_BODY)"
|
||||
fi
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
### 2.1 프로젝트 구조 (통합)
|
||||
|
||||
**단일 앱 구조** (소규모 프로젝트 최적화):
|
||||
**단일 앱 구조** (공개 사이트 + 관리자까지 하나의 ASP.NET Core 앱):
|
||||
|
||||
```
|
||||
TaxBaik.Domain 클래스 라이브러리 (엔티티, 인터페이스, enum)
|
||||
@@ -32,7 +32,7 @@ TaxBaik.Web ASP.NET Core 앱 (포트 5001)
|
||||
|
||||
**경로:**
|
||||
- 홈페이지: `/taxbaik` (Razor Pages)
|
||||
- 관리자: `/taxbaik/admin` (Blazor)
|
||||
- 관리자: `/taxbaik/admin` (Blazor Server)
|
||||
- 로그인: `/taxbaik/admin/login`
|
||||
|
||||
### 2.2 계층 책임
|
||||
@@ -54,11 +54,11 @@ TaxBaik.Web ASP.NET Core 앱 (포트 5001)
|
||||
- 복잡한 관리 UI를 쉽게 구현
|
||||
|
||||
**왜 단일 앱 (통합 Web)인가?**
|
||||
- 소규모 프로젝트 → 분리의 이점 < 개발 복잡도
|
||||
- 공개 사이트와 관리자 화면을 같은 호스트와 PathBase에서 운영하면 라우팅과 인증 구성이 단순함
|
||||
- **개발**: 터미널 1개, 포트 1개 (5001)
|
||||
- **배포**: 앱 1개, DB 마이그레이션 1회
|
||||
- **유지보수**: 모든 비즈니스 로직 한 곳 (Application)
|
||||
- **장점**: 기존 분리 구조의 모든 기능 + 간단한 개발 경험
|
||||
- **장점**: 블로그 SEO와 관리자 기능을 하나의 실행 단위로 운영
|
||||
|
||||
**왜 Dapper인가?**
|
||||
- 팀 기존 지식 (QuantEngine에서 사용)
|
||||
@@ -112,7 +112,7 @@ dotnet run -p TaxBaik.Web
|
||||
# 터미널 1: SSH 터널 유지
|
||||
ssh -L 5432:127.0.0.1:5432 kjh2064@178.104.200.7
|
||||
|
||||
# 터미널 2: 통합 Web 앱 (Razor Pages + Blazor)
|
||||
# 터미널 2: 통합 Web 앱 (Razor Pages + Blazor Server Admin)
|
||||
cd TaxBaik.Web
|
||||
dotnet run
|
||||
# 접속:
|
||||
@@ -248,48 +248,23 @@ ssh kjh2064@178.104.200.7
|
||||
5432 : PostgreSQL (localhost 바인드)
|
||||
```
|
||||
|
||||
### 3.3 배포 절차 (Shadow Copy를 통한 Hot Deploy)
|
||||
### 3.3 배포 절차 (CI only)
|
||||
|
||||
**핵심 전략**: .NET Core shadow copy로 배포 중 무중단 실행
|
||||
배포는 수동 실행이 아니라 **Gitea Actions CI/CD**만 사용한다.
|
||||
|
||||
1. **로컬 빌드** (단일 앱 통합):
|
||||
```bash
|
||||
dotnet clean TaxBaik.sln
|
||||
dotnet publish TaxBaik.Web -c Release -o ./publish
|
||||
```
|
||||
1. `master` 브랜치에 push
|
||||
2. Gitea Actions가 `TaxBaik.Web`을 build/publish
|
||||
3. CI가 서버의 `taxbaik` 서비스와 `~/taxbaik_active`를 갱신
|
||||
4. CI가 서비스 재시작 후 `/taxbaik/admin/login`으로 헬스 체크
|
||||
|
||||
2. **CI/CD 배포** (Gitea Actions):
|
||||
- 새 버전을 `~/deployments/taxbaik_TIMESTAMP/` 에 업로드
|
||||
- 기존 프로세스는 계속 실행 (원본 DLL은 영향 없음)
|
||||
**운영 규칙**:
|
||||
- 로컬 또는 서버에서 수동 `dotnet publish`로 운영 배포하지 않는다
|
||||
- `rsync`로 직접 아티팩트를 올리지 않는다
|
||||
- 배포 실패 시 CI 로그를 먼저 본다
|
||||
|
||||
3. **Shadow Copy 메커니즘**:
|
||||
- .NET Core 런타임이 어셈블리를 메모리에 로드
|
||||
- `~/deployments/` 아래의 새 DLL들을 준비
|
||||
- 심링크만 변경 (`ln -sfn ~/deployments/taxbaik_TIMESTAMP ~/taxbaik_active`)
|
||||
|
||||
4. **Graceful Restart**:
|
||||
- 기존 요청 완료 대기 (max 30초)
|
||||
- `sudo systemctl restart taxbaik` 실행
|
||||
- 새 프로세스가 새 DLL 로드
|
||||
|
||||
5. **롤백 (1초 이내)**:
|
||||
```bash
|
||||
ln -sfn ~/deployments/taxbaik_PREVIOUS_TIMESTAMP ~/taxbaik_active
|
||||
sudo systemctl restart taxbaik
|
||||
```
|
||||
|
||||
6. **오래된 배포 정리** (매 배포마다):
|
||||
```bash
|
||||
# 최근 5개 배포만 유지
|
||||
ls -dt ~/deployments/taxbaik_* | tail -n +6 | xargs -r rm -rf
|
||||
```
|
||||
|
||||
**systemd 서비스 graceful shutdown 설정**:
|
||||
```ini
|
||||
[Service]
|
||||
TimeoutStopSec=35 # 기존 요청 완료 대기 (30초) + 여유
|
||||
KillMode=mixed # SIGTERM → 30초 대기 → SIGKILL
|
||||
```
|
||||
**롤백**:
|
||||
- 이전 정상 커밋을 `master`에 revert 또는 hotfix로 되돌린다
|
||||
- 서버 파일을 수동으로 복구하지 않는다
|
||||
|
||||
### 3.4 서비스 파일 위치
|
||||
```
|
||||
@@ -297,14 +272,7 @@ KillMode=mixed # SIGTERM → 30초 대기 → SIGKILL
|
||||
```
|
||||
|
||||
### 5.5 배포 디렉토리 구조 (서버)
|
||||
```
|
||||
/home/kjh2064/
|
||||
├── taxbaik_active → ./deployments/taxbaik_20260626_150000/
|
||||
└── deployments/
|
||||
├── taxbaik_20260626_150000/ (통합 Web publish 출력)
|
||||
├── taxbaik_20260626_140000/ (이전 버전)
|
||||
└── ...
|
||||
```
|
||||
배포 디렉토리는 CI가 관리한다. 로컬에서 구조를 맞추거나 수동으로 갱신하지 않는다.
|
||||
|
||||
---
|
||||
|
||||
@@ -319,7 +287,7 @@ 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 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;
|
||||
@@ -328,7 +296,7 @@ location /taxbaik {
|
||||
}
|
||||
```
|
||||
|
||||
**참고**: 단일 `/taxbaik` 블록이 공개 사이트와 관리자 (Blazor WebSocket)를 모두 처리합니다.
|
||||
**참고**: 단일 `/taxbaik` 블록이 공개 사이트와 관리자(Blazor WebSocket)를 모두 처리합니다. 운영은 `5001` 통합 앱 기준이며, 설정 반영은 CI 배포로만 수행한다.
|
||||
|
||||
---
|
||||
|
||||
@@ -373,7 +341,7 @@ public async Task<BlogPost?> GetBySlugAsync(string slug, CancellationToken ct)
|
||||
- 항상 `using var conn = Conn();` 사용 (자동 닫기)
|
||||
- 항상 `@ParameterName` 파라미터 사용 (SQL injection 방지)
|
||||
- 절대 문자열 연결 금지
|
||||
- 대소문자 구분 안 함 (Dapper가 매핑)
|
||||
- PostgreSQL `snake_case` 컬럼은 Dapper underscore 매핑을 전제로 함
|
||||
|
||||
### 3.3 마이그레이션
|
||||
|
||||
|
||||
+11
-33
@@ -27,21 +27,12 @@ Environment=ASPNETCORE_URLS=http://127.0.0.1:5001
|
||||
Environment=ConnectionStrings__Default=Host=localhost;Database=taxbaikdb;Username=taxbaik;Password=your_secure_password
|
||||
```
|
||||
|
||||
**Admin 서비스** (`/etc/systemd/system/taxbaik-admin.service`):
|
||||
```ini
|
||||
[Service]
|
||||
Environment=ASPNETCORE_ENVIRONMENT=Production
|
||||
Environment=ASPNETCORE_URLS=http://127.0.0.1:5002
|
||||
Environment=ConnectionStrings__Default=Host=localhost;Database=taxbaikdb;Username=taxbaik;Password=your_secure_password
|
||||
```
|
||||
|
||||
### 3. systemd 서비스 파일 설치
|
||||
|
||||
```bash
|
||||
sudo cp deploy/taxbaik.service /etc/systemd/system/
|
||||
sudo cp deploy/taxbaik-admin.service /etc/systemd/system/
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable taxbaik taxbaik-admin
|
||||
sudo systemctl enable taxbaik
|
||||
```
|
||||
|
||||
### 4. Nginx 설정
|
||||
@@ -69,27 +60,10 @@ sudo systemctl reload nginx
|
||||
|
||||
2. 배포 워크플로우는 자동으로 실행:
|
||||
```
|
||||
master 브랜치 push → build → publish → rsync → restart
|
||||
master 브랜치 push → build → publish → restart
|
||||
```
|
||||
|
||||
### 수동 배포 (필요시)
|
||||
|
||||
```bash
|
||||
# 로컬에서 빌드
|
||||
dotnet publish TaxBaik.sln -c Release -o ./publish
|
||||
|
||||
# 서버에 배포
|
||||
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
||||
rsync -az ./publish/web/ kjh2064@178.104.200.7:~/deployments/taxbaik_${TIMESTAMP}/
|
||||
rsync -az ./publish/admin/ kjh2064@178.104.200.7:~/deployments/taxbaik_admin_${TIMESTAMP}/
|
||||
|
||||
# 서버에서 심링크 변경 및 재시작
|
||||
ssh kjh2064@178.104.200.7 << EOF
|
||||
ln -sfn ~/deployments/taxbaik_${TIMESTAMP} ~/taxbaik_active
|
||||
ln -sfn ~/deployments/taxbaik_admin_${TIMESTAMP} ~/taxbaik_admin_active
|
||||
sudo systemctl restart taxbaik taxbaik-admin
|
||||
EOF
|
||||
```
|
||||
수동 배포는 사용하지 않습니다. 배포 이슈는 Gitea Actions 로그로 해결합니다.
|
||||
|
||||
## 마이그레이션 자동 실행
|
||||
|
||||
@@ -102,7 +76,6 @@ EOF
|
||||
로그 확인:
|
||||
```bash
|
||||
journalctl -u taxbaik -n 50
|
||||
journalctl -u taxbaik-admin -n 50
|
||||
```
|
||||
|
||||
## 검증
|
||||
@@ -116,6 +89,11 @@ 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":"admin123"}'
|
||||
|
||||
# 문의 폼 제출 테스트
|
||||
curl -X POST http://178.104.200.7/taxbaik/contact \
|
||||
-H "Content-Type: application/x-www-form-urlencoded" \
|
||||
@@ -158,10 +136,10 @@ sudo systemctl restart taxbaik
|
||||
ssh kjh2064@178.104.200.7
|
||||
|
||||
# 서비스 상태
|
||||
systemctl status taxbaik taxbaik-admin
|
||||
systemctl status taxbaik
|
||||
|
||||
# 포트 확인
|
||||
netstat -tlnp | grep -E '5001|5002'
|
||||
netstat -tlnp | grep -E '5001'
|
||||
|
||||
# 프로세스 확인
|
||||
ps aux | grep TaxBaik
|
||||
@@ -175,7 +153,7 @@ tail -f /var/log/nginx/access.log | grep taxbaik
|
||||
|
||||
# 애플리케이션 로그
|
||||
journalctl -u taxbaik -f
|
||||
journalctl -u taxbaik-admin -f
|
||||
journalctl -u taxbaik -f
|
||||
```
|
||||
|
||||
## 트러블슈팅
|
||||
|
||||
+7
-11
@@ -12,7 +12,6 @@
|
||||
# 1단계: 빌드 (이미 완료됨)
|
||||
cd C:\Temp\taxbaik
|
||||
dotnet publish TaxBaik.Web -c Release -o ./publish/web
|
||||
dotnet publish TaxBaik.Admin -c Release -o ./publish/admin
|
||||
|
||||
# 2단계: Docker Compose 실행
|
||||
docker-compose up -d
|
||||
@@ -31,7 +30,7 @@ docker-compose ps
|
||||
- 상담 신청 폼
|
||||
|
||||
### 관리자 백오피스 (Blazor Server)
|
||||
- **URL**: http://localhost:5002/taxbaik/admin/login
|
||||
- **URL**: http://localhost:5001/taxbaik/admin/login
|
||||
- **초기 계정**:
|
||||
- username: `admin`
|
||||
- password: `admin123`
|
||||
@@ -71,9 +70,6 @@ SELECT username FROM admin_users;
|
||||
# Web 앱 로그
|
||||
docker-compose logs -f taxbaik-web
|
||||
|
||||
# Admin 앱 로그
|
||||
docker-compose logs -f taxbaik-admin
|
||||
|
||||
# 데이터베이스 로그
|
||||
docker-compose logs -f postgres
|
||||
```
|
||||
@@ -103,16 +99,16 @@ curl -X POST http://localhost:5001/taxbaik/contact \
|
||||
### 6.3 관리자 테스트
|
||||
```bash
|
||||
# 로그인 페이지
|
||||
curl -I http://localhost:5002/taxbaik/admin/login
|
||||
curl -I http://localhost:5001/taxbaik/admin/login
|
||||
# 예상: 200 OK
|
||||
|
||||
# 로그인 (쿠키 저장)
|
||||
curl -c cookies.txt -X POST http://localhost:5002/taxbaik/admin/login \
|
||||
curl -c cookies.txt -X POST http://localhost:5001/taxbaik/admin/login \
|
||||
-H "Content-Type: application/x-www-form-urlencoded" \
|
||||
-d "username=admin&password=admin123"
|
||||
|
||||
# 대시보드 접근 (쿠키 사용)
|
||||
curl -b cookies.txt http://localhost:5002/taxbaik/admin/dashboard
|
||||
curl -b cookies.txt http://localhost:5001/taxbaik/admin/dashboard
|
||||
```
|
||||
|
||||
## 7. 종료 및 정리
|
||||
@@ -125,14 +121,14 @@ docker-compose down
|
||||
docker-compose down -v
|
||||
|
||||
# 이미지 삭제
|
||||
docker rmi taxbaik-web taxbaik-admin
|
||||
docker rmi taxbaik-web
|
||||
```
|
||||
|
||||
## 8. 트러블슈팅
|
||||
|
||||
| 문제 | 해결 방법 |
|
||||
|------|----------|
|
||||
| 포트 5001/5002 사용 중 | `netstat -ano \| findstr :5001` 후 프로세스 종료 |
|
||||
| 포트 5001 사용 중 | `netstat -ano \| findstr :5001` 후 프로세스 종료 |
|
||||
| 데이터베이스 연결 실패 | `docker-compose logs postgres` 로그 확인 |
|
||||
| 마이그레이션 오류 | `docker-compose down -v` 후 재시작 |
|
||||
| 메모리 부족 | Docker Desktop 설정에서 메모리 증가 |
|
||||
@@ -169,5 +165,5 @@ docker-compose exec postgres psql -U taxbaik -d taxbaikdb \
|
||||
|
||||
**상태 확인 URL**:
|
||||
- 공개 사이트: http://localhost:5001/taxbaik
|
||||
- 관리자: http://localhost:5002/taxbaik/admin/login
|
||||
- 관리자: http://localhost:5001/taxbaik/admin/login
|
||||
- 데이터베이스: localhost:5432
|
||||
|
||||
@@ -40,8 +40,7 @@ TaxBaik/
|
||||
├── TaxBaik.Domain/ # 비즈니스 규칙, 엔티티, 인터페이스
|
||||
├── TaxBaik.Infrastructure/ # DB 접근, Dapper 구현체, 마이그레이션
|
||||
├── TaxBaik.Application/ # 서비스, DTO, 비즈니스 워크플로우
|
||||
├── TaxBaik.Web/ # Razor Pages 공개 사이트 (port 5001)
|
||||
├── TaxBaik.Admin/ # Blazor Server 관리자 (port 5002)
|
||||
├── TaxBaik.Web/ # Razor Pages + 관리자 통합 앱 (port 5001)
|
||||
├── db/migrations/ # 데이터베이스 마이그레이션 SQL
|
||||
├── deploy/ # systemd 서비스 파일, Nginx 설정
|
||||
└── .gitea/workflows/ # CI/CD 파이프라인
|
||||
@@ -75,7 +74,7 @@ TaxBaik/
|
||||
- 이미지 lazy load
|
||||
- CSS/JS 최적화
|
||||
|
||||
### 관리자 백오피스 (TaxBaik.Admin)
|
||||
### 관리자 백오피스 (TaxBaik.Web 내 Blazor Server)
|
||||
|
||||
- **대시보드**
|
||||
- 이번달 문의 수
|
||||
@@ -130,7 +129,7 @@ dotnet run --project TaxBaik.Web
|
||||
|
||||
# 5. 브라우저 열기
|
||||
# 공개 사이트: http://localhost:5001/taxbaik
|
||||
# 관리자: http://localhost:5002/taxbaik/admin
|
||||
# 관리자: http://localhost:5001/taxbaik/admin/login
|
||||
```
|
||||
|
||||
### 초기 로그인 정보
|
||||
@@ -144,24 +143,23 @@ dotnet run --project TaxBaik.Web
|
||||
|
||||
## 배포
|
||||
|
||||
### 자동 배포 (Gitea Actions)
|
||||
### 배포 방식
|
||||
|
||||
배포는 **Gitea Actions CI/CD**만 사용합니다.
|
||||
|
||||
master 브랜치에 푸시하면 자동으로:
|
||||
1. ✅ .NET 빌드 (Release)
|
||||
2. ✅ 단위 테스트 실행
|
||||
3. ✅ Web & Admin 게시
|
||||
4. ✅ 서버에 rsync로 업로드
|
||||
5. ✅ 심링크 스왑 (무중단 배포)
|
||||
6. ✅ 서비스 재시작
|
||||
3. ✅ `TaxBaik.Web` 게시
|
||||
4. ✅ 서버 반영 및 서비스 재시작
|
||||
5. ✅ `/taxbaik/`, `/taxbaik/admin/login`, `/taxbaik/api/auth/login` 헬스 체크
|
||||
|
||||
**필수 Gitea Secrets 설정:**
|
||||
- `DEPLOY_USER`: kjh2064
|
||||
- `DEPLOY_HOST`: 178.104.200.7
|
||||
- `DEPLOY_SSH_KEY`: SSH 개인키 (줄바꿈 포함)
|
||||
|
||||
### 수동 배포
|
||||
|
||||
[DEPLOYMENT_GUIDE.md](./DEPLOYMENT_GUIDE.md) 참고
|
||||
수동 배포는 사용하지 않습니다. 실패 시 [DEPLOYMENT_GUIDE.md](./DEPLOYMENT_GUIDE.md)의 CI 점검 절차를 따릅니다.
|
||||
|
||||
---
|
||||
|
||||
@@ -243,7 +241,7 @@ psql -U taxbaik -d taxbaikdb -c "DELETE FROM schema_migrations WHERE version='00
|
||||
```bash
|
||||
# 포트 확인
|
||||
lsof -i :5001
|
||||
lsof -i :5002
|
||||
lsof -i :5001
|
||||
|
||||
# 프로세스 종료
|
||||
kill -9 <PID>
|
||||
|
||||
+4
-38
@@ -126,28 +126,23 @@ SELECT * FROM categories;
|
||||
```bash
|
||||
# 로컬에서:
|
||||
scp deploy/taxbaik.service kjh2064@178.104.200.7:~/
|
||||
scp deploy/taxbaik-admin.service kjh2064@178.104.200.7:~/
|
||||
```
|
||||
|
||||
서버에서:
|
||||
```bash
|
||||
# 파일 복사
|
||||
sudo cp ~/taxbaik.service /etc/systemd/system/
|
||||
sudo cp ~/taxbaik-admin.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
|
||||
|
||||
# 같은 작업을 taxbaik-admin.service에도 반복
|
||||
|
||||
# systemd 재로드
|
||||
sudo systemctl daemon-reload
|
||||
|
||||
# 서비스 활성화 (부팅 시 자동 시작)
|
||||
sudo systemctl enable taxbaik
|
||||
sudo systemctl enable taxbaik-admin
|
||||
```
|
||||
|
||||
---
|
||||
@@ -170,24 +165,14 @@ ls -la /etc/nginx/sites-available/
|
||||
location /taxbaik {
|
||||
proxy_pass http://127.0.0.1:5001;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Connection keep-alive;
|
||||
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;
|
||||
}
|
||||
|
||||
location /taxbaik/admin {
|
||||
proxy_pass http://127.0.0.1:5002;
|
||||
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;
|
||||
}
|
||||
```
|
||||
|
||||
설정 검증 및 재로드:
|
||||
@@ -229,8 +214,6 @@ Gitea Secret 창에 전체 붙여넣기.
|
||||
```bash
|
||||
mkdir -p ~/deployments
|
||||
mkdir -p ~/taxbaik_active
|
||||
mkdir -p ~/taxbaik_admin_active
|
||||
|
||||
# 권한 확인
|
||||
ls -la ~/ | grep taxbaik
|
||||
```
|
||||
@@ -244,21 +227,8 @@ ls -la ~/ | grep taxbaik
|
||||
# 솔루션 빌드
|
||||
dotnet build TaxBaik.sln -c Release
|
||||
|
||||
# Web 앱 발행
|
||||
dotnet publish src/TaxBaik.Web/ -c Release -o ./publish/web
|
||||
|
||||
# 서버에 배포
|
||||
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
||||
rsync -az ./publish/web/ kjh2064@178.104.200.7:~/deployments/taxbaik_$TIMESTAMP/
|
||||
|
||||
# 서버에서 심링크 설정
|
||||
ssh kjh2064@178.104.200.7 "ln -sfn ~/deployments/taxbaik_$TIMESTAMP ~/taxbaik_active"
|
||||
|
||||
# 서비스 시작
|
||||
ssh kjh2064@178.104.200.7 "sudo systemctl start taxbaik"
|
||||
|
||||
# 확인
|
||||
curl http://178.104.200.7/taxbaik
|
||||
# 배포는 Gitea Actions가 처리
|
||||
# 수동 publish/rsync 절차는 사용하지 않음
|
||||
```
|
||||
|
||||
---
|
||||
@@ -274,18 +244,14 @@ psql -U taxbaik -d taxbaikdb -c "\dt"
|
||||
|
||||
# 2. 서비스 상태
|
||||
sudo systemctl status taxbaik
|
||||
sudo systemctl status taxbaik-admin
|
||||
|
||||
# 3. Nginx 로그 확인
|
||||
sudo tail -f /var/log/nginx/error.log
|
||||
|
||||
# 4. 앱 로그 확인
|
||||
journalctl -u taxbaik -n 50
|
||||
journalctl -u taxbaik-admin -n 50
|
||||
|
||||
# 5. 엔드포인트 테스트
|
||||
curl -v http://127.0.0.1:5001/health
|
||||
curl -v http://127.0.0.1:5002/health
|
||||
curl -v http://127.0.0.1/taxbaik
|
||||
|
||||
# 6. 문의 폼 E2E 테스트
|
||||
|
||||
@@ -3,10 +3,17 @@ namespace TaxBaik.Infrastructure.Data;
|
||||
using System.Data;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Npgsql;
|
||||
using Dapper;
|
||||
using TaxBaik.Domain.Interfaces;
|
||||
|
||||
public sealed class DbConnectionFactory : IDbConnectionFactory
|
||||
{
|
||||
static DbConnectionFactory()
|
||||
{
|
||||
// Keep PostgreSQL snake_case columns aligned with C# PascalCase properties.
|
||||
DefaultTypeMap.MatchNamesWithUnderscores = true;
|
||||
}
|
||||
|
||||
private readonly string _connectionString;
|
||||
|
||||
public DbConnectionFactory(IConfiguration configuration)
|
||||
|
||||
@@ -98,6 +98,33 @@ public class MigrationRunner
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var assembly = Assembly.GetExecutingAssembly();
|
||||
var resourceNames = assembly.GetManifestResourceNames()
|
||||
.Where(x => x.Contains(".Migrations.V") && x.EndsWith(".sql", StringComparison.OrdinalIgnoreCase))
|
||||
.OrderBy(x => x);
|
||||
|
||||
foreach (var resourceName in resourceNames)
|
||||
{
|
||||
using var stream = assembly.GetManifestResourceStream(resourceName);
|
||||
if (stream == null)
|
||||
continue;
|
||||
|
||||
using var reader = new StreamReader(stream);
|
||||
var sql = reader.ReadToEnd();
|
||||
var fileName = Path.GetFileNameWithoutExtension(resourceName);
|
||||
var versionStart = fileName.IndexOf('V');
|
||||
var versionEnd = fileName.IndexOf('_', versionStart + 1);
|
||||
if (versionStart < 0 || versionEnd < 0)
|
||||
continue;
|
||||
|
||||
var version = fileName.Substring(versionStart + 1, versionEnd - versionStart - 1);
|
||||
var description = fileName.Substring(versionEnd + 1);
|
||||
|
||||
migrations.Add(new Migration { Version = version, Description = description, Sql = sql });
|
||||
}
|
||||
}
|
||||
|
||||
return migrations;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,16 @@ public class AdminUserRepository : BaseRepository, IAdminUserRepository
|
||||
{
|
||||
using var conn = _connectionFactory.CreateConnection();
|
||||
return await conn.QueryFirstOrDefaultAsync<AdminUser>(
|
||||
"SELECT * FROM admin_users WHERE username = @username",
|
||||
"""
|
||||
SELECT
|
||||
id,
|
||||
username,
|
||||
password_hash AS PasswordHash,
|
||||
last_login_at AS LastLoginAt,
|
||||
created_at AS CreatedAt
|
||||
FROM admin_users
|
||||
WHERE username = @username
|
||||
""",
|
||||
new { username });
|
||||
}
|
||||
|
||||
@@ -20,7 +29,16 @@ public class AdminUserRepository : BaseRepository, IAdminUserRepository
|
||||
{
|
||||
using var conn = _connectionFactory.CreateConnection();
|
||||
return await conn.QueryFirstOrDefaultAsync<AdminUser>(
|
||||
"SELECT * FROM admin_users WHERE id = @id",
|
||||
"""
|
||||
SELECT
|
||||
id,
|
||||
username,
|
||||
password_hash AS PasswordHash,
|
||||
last_login_at AS LastLoginAt,
|
||||
created_at AS CreatedAt
|
||||
FROM admin_users
|
||||
WHERE id = @id
|
||||
""",
|
||||
new { id });
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
@inherits LayoutComponentBase
|
||||
|
||||
<MudThemeProvider />
|
||||
<MudDialogProvider />
|
||||
<MudSnackbarProvider />
|
||||
|
||||
@Body
|
||||
|
||||
@@ -9,8 +9,6 @@
|
||||
<PageTitle>로그인</PageTitle>
|
||||
|
||||
<MudThemeProvider />
|
||||
<MudDialogProvider />
|
||||
<MudSnackbarProvider />
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.Small" Class="d-flex align-center justify-center" Style="min-height: 100vh;">
|
||||
<MudPaper Class="pa-8" Elevation="3" Style="width: 100%; max-width: 400px;">
|
||||
|
||||
@@ -36,6 +36,12 @@ public class AuthService
|
||||
return null;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(user.PasswordHash))
|
||||
{
|
||||
_logger.LogError("로그인 실패: 사용자 '{Username}'의 PasswordHash가 비어 있습니다.", username);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!BCrypt.Net.BCrypt.Verify(password, user.PasswordHash))
|
||||
{
|
||||
_logger.LogWarning("로그인 시도: 잘못된 비밀번호 '{Username}'", username);
|
||||
|
||||
@@ -22,19 +22,9 @@ cp -r "$1/web" "$WEB_DEPLOY_DIR/"
|
||||
ln -sfn "$WEB_DEPLOY_DIR/web" "$DEPLOY_HOME/taxbaik_active"
|
||||
echo "✓ Web symlink updated: $WEB_DEPLOY_DIR/web"
|
||||
|
||||
# Admin 배포
|
||||
echo "=== Deploying Admin ==="
|
||||
ADMIN_TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
||||
ADMIN_DEPLOY_DIR="$DEPLOY_HOME/deployments/taxbaik_admin_${ADMIN_TIMESTAMP}"
|
||||
mkdir -p "$ADMIN_DEPLOY_DIR"
|
||||
cp -r "$1/admin" "$ADMIN_DEPLOY_DIR/"
|
||||
ln -sfn "$ADMIN_DEPLOY_DIR/admin" "$DEPLOY_HOME/taxbaik_admin_active"
|
||||
echo "✓ Admin symlink updated: $ADMIN_DEPLOY_DIR/admin"
|
||||
|
||||
# 프로세스 재시작
|
||||
echo "=== Restarting processes ==="
|
||||
pkill -9 -f "TaxBaik.Web" || true
|
||||
pkill -9 -f "TaxBaik.Admin" || true
|
||||
sleep 3
|
||||
|
||||
echo "=== Starting Web ==="
|
||||
@@ -46,13 +36,6 @@ nohup /usr/local/dotnet/dotnet TaxBaik.Web.dll > web.log 2>&1 &
|
||||
sleep 2
|
||||
ps aux | grep TaxBaik.Web | grep -v grep && echo "✓ Web started" || echo "✗ Web failed"
|
||||
|
||||
echo "=== Starting Admin ==="
|
||||
cd "$DEPLOY_HOME/taxbaik_admin_active"
|
||||
export ASPNETCORE_URLS=http://127.0.0.1:5002
|
||||
nohup /usr/local/dotnet/dotnet TaxBaik.Admin.dll > admin.log 2>&1 &
|
||||
sleep 2
|
||||
ps aux | grep TaxBaik.Admin | grep -v grep && echo "✓ Admin started" || echo "✗ Admin failed"
|
||||
|
||||
echo ""
|
||||
echo "===== ✅ 배포 완료 ====="
|
||||
cat "$DEPLOY_HOME/taxbaik_active/wwwroot/version.txt" 2>/dev/null || echo "Version file not found"
|
||||
|
||||
@@ -1,26 +1,15 @@
|
||||
# TaxBaik Nginx Location Blocks
|
||||
# Add these to your main Nginx config (e.g., /etc/nginx/sites-available/default)
|
||||
|
||||
# TaxBaik 공개 사이트 (Razor Pages)
|
||||
# TaxBaik 공개 사이트 + 관리자 (통합 앱)
|
||||
location /taxbaik {
|
||||
proxy_pass http://127.0.0.1:5001;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Connection keep-alive;
|
||||
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;
|
||||
}
|
||||
|
||||
# TaxBaik 관리자 (Blazor Server - WebSocket 필요)
|
||||
location /taxbaik/admin {
|
||||
proxy_pass http://127.0.0.1:5002;
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
[Unit]
|
||||
Description=TaxBaik Admin Backoffice (.NET 8 Blazor Server)
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=kjh2064
|
||||
WorkingDirectory=/home/kjh2064/taxbaik_admin_active
|
||||
ExecStart=/usr/bin/dotnet TaxBaik.Admin.dll
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
|
||||
# Graceful Shutdown (Hot Deploy용)
|
||||
TimeoutStopSec=35
|
||||
KillMode=mixed
|
||||
KillSignal=SIGTERM
|
||||
|
||||
SyslogIdentifier=taxbaik-admin
|
||||
Environment=ASPNETCORE_ENVIRONMENT=Production
|
||||
Environment=ASPNETCORE_URLS=http://127.0.0.1:5002
|
||||
Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false
|
||||
# 아래 줄은 서버에서 직접 편집 (git에 커밋하지 않음)
|
||||
# Environment=ConnectionStrings__Default=Host=localhost;Database=taxbaikdb;Username=taxbaik;Password=CHANGE_ME
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
Reference in New Issue
Block a user