From 1d7dd710115d7dc870c163b3513e50e49432a42d Mon Sep 17 00:00:00 2001 From: kjh2064 Date: Sat, 27 Jun 2026 01:34:17 +0900 Subject: [PATCH] fix: unify TaxBaik deployment around CI --- .claude/scheduled_tasks.lock | 1 - .gitea/workflows/deploy.yml | 18 +++-- CLAUDE.md | 76 ++++++------------- DEPLOYMENT_GUIDE.md | 44 +++-------- DOCKER_RUN.md | 18 ++--- README.md | 24 +++--- SERVER_SETUP_GUIDE.md | 42 +--------- .../Data/DbConnectionFactory.cs | 7 ++ .../Data/MigrationRunner.cs | 27 +++++++ .../Repositories/AdminUserRepository.cs | 22 +++++- .../Components/Admin/Layout/BlankLayout.razor | 4 - .../Components/Admin/Pages/Login.razor | 2 - TaxBaik.Web/Services/AuthService.cs | 6 ++ deploy.sh | 17 ----- deploy/nginx-taxbaik-locations.conf | 17 +---- deploy/taxbaik-admin.service | 26 ------- 16 files changed, 130 insertions(+), 221 deletions(-) delete mode 100644 .claude/scheduled_tasks.lock delete mode 100644 deploy/taxbaik-admin.service diff --git a/.claude/scheduled_tasks.lock b/.claude/scheduled_tasks.lock deleted file mode 100644 index 408ae32..0000000 --- a/.claude/scheduled_tasks.lock +++ /dev/null @@ -1 +0,0 @@ -{"sessionId":"c3cb93c0-7adf-4d3a-817d-6c01e0e0f09f","pid":26816,"acquiredAt":1782481349474} \ No newline at end of file diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index 51b3f37..efededb 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -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 diff --git a/CLAUDE.md b/CLAUDE.md index ec72423..f57bc0c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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 GetBySlugAsync(string slug, CancellationToken ct) - 항상 `using var conn = Conn();` 사용 (자동 닫기) - 항상 `@ParameterName` 파라미터 사용 (SQL injection 방지) - 절대 문자열 연결 금지 -- 대소문자 구분 안 함 (Dapper가 매핑) +- PostgreSQL `snake_case` 컬럼은 Dapper underscore 매핑을 전제로 함 ### 3.3 마이그레이션 diff --git a/DEPLOYMENT_GUIDE.md b/DEPLOYMENT_GUIDE.md index d0a92ef..0771847 100644 --- a/DEPLOYMENT_GUIDE.md +++ b/DEPLOYMENT_GUIDE.md @@ -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 ``` ## 트러블슈팅 diff --git a/DOCKER_RUN.md b/DOCKER_RUN.md index 3bbd62c..1f867a8 100644 --- a/DOCKER_RUN.md +++ b/DOCKER_RUN.md @@ -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 diff --git a/README.md b/README.md index adb8f79..b8718b8 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/SERVER_SETUP_GUIDE.md b/SERVER_SETUP_GUIDE.md index 8f5d5d1..6ed724f 100644 --- a/SERVER_SETUP_GUIDE.md +++ b/SERVER_SETUP_GUIDE.md @@ -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 테스트 diff --git a/TaxBaik.Infrastructure/Data/DbConnectionFactory.cs b/TaxBaik.Infrastructure/Data/DbConnectionFactory.cs index 4cc6230..42cd115 100644 --- a/TaxBaik.Infrastructure/Data/DbConnectionFactory.cs +++ b/TaxBaik.Infrastructure/Data/DbConnectionFactory.cs @@ -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) diff --git a/TaxBaik.Infrastructure/Data/MigrationRunner.cs b/TaxBaik.Infrastructure/Data/MigrationRunner.cs index 994d164..de1ebbd 100644 --- a/TaxBaik.Infrastructure/Data/MigrationRunner.cs +++ b/TaxBaik.Infrastructure/Data/MigrationRunner.cs @@ -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; } diff --git a/TaxBaik.Infrastructure/Repositories/AdminUserRepository.cs b/TaxBaik.Infrastructure/Repositories/AdminUserRepository.cs index 55568ae..da1bda3 100644 --- a/TaxBaik.Infrastructure/Repositories/AdminUserRepository.cs +++ b/TaxBaik.Infrastructure/Repositories/AdminUserRepository.cs @@ -12,7 +12,16 @@ public class AdminUserRepository : BaseRepository, IAdminUserRepository { using var conn = _connectionFactory.CreateConnection(); return await conn.QueryFirstOrDefaultAsync( - "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( - "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 }); } diff --git a/TaxBaik.Web/Components/Admin/Layout/BlankLayout.razor b/TaxBaik.Web/Components/Admin/Layout/BlankLayout.razor index 08d9c34..4f3f76d 100644 --- a/TaxBaik.Web/Components/Admin/Layout/BlankLayout.razor +++ b/TaxBaik.Web/Components/Admin/Layout/BlankLayout.razor @@ -1,7 +1,3 @@ @inherits LayoutComponentBase - - - - @Body diff --git a/TaxBaik.Web/Components/Admin/Pages/Login.razor b/TaxBaik.Web/Components/Admin/Pages/Login.razor index 17c9987..c1d90b0 100644 --- a/TaxBaik.Web/Components/Admin/Pages/Login.razor +++ b/TaxBaik.Web/Components/Admin/Pages/Login.razor @@ -9,8 +9,6 @@ 로그인 - - diff --git a/TaxBaik.Web/Services/AuthService.cs b/TaxBaik.Web/Services/AuthService.cs index a83b99b..c27b562 100644 --- a/TaxBaik.Web/Services/AuthService.cs +++ b/TaxBaik.Web/Services/AuthService.cs @@ -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); diff --git a/deploy.sh b/deploy.sh index 693cedb..3caf8c0 100644 --- a/deploy.sh +++ b/deploy.sh @@ -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" diff --git a/deploy/nginx-taxbaik-locations.conf b/deploy/nginx-taxbaik-locations.conf index dba07c9..2b59784 100644 --- a/deploy/nginx-taxbaik-locations.conf +++ b/deploy/nginx-taxbaik-locations.conf @@ -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; -} diff --git a/deploy/taxbaik-admin.service b/deploy/taxbaik-admin.service deleted file mode 100644 index 012051d..0000000 --- a/deploy/taxbaik-admin.service +++ /dev/null @@ -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