diff --git a/.gitea/workflows/browser-e2e.yml b/.gitea/workflows/browser-e2e.yml index d8ae63d..d250d7c 100644 --- a/.gitea/workflows/browser-e2e.yml +++ b/.gitea/workflows/browser-e2e.yml @@ -49,12 +49,13 @@ jobs: # Suppress stderr and allow failures to handle transition/down periods cleanly VERSION_BODY="$(curl -fsS "http://${DEPLOY_HOST}/taxbaik/version.json" 2>/dev/null || true)" BLOG_STATUS="$(curl -s -o /dev/null -w '%{http_code}' "http://${DEPLOY_HOST}/taxbaik/blog/accountant-mistakes-5" || true)" - if echo "$VERSION_BODY" | grep -q "\"version\": \"${SHORT_VERSION}\"" && [ "$BLOG_STATUS" = "200" ]; then + LOGIN_STATUS="$(curl -s -o /dev/null -w '%{http_code}' "http://${DEPLOY_HOST}/taxbaik/admin/login" || true)" + if echo "$VERSION_BODY" | grep -q "\"version\": \"${SHORT_VERSION}\"" && [ "$BLOG_STATUS" = "200" ] && [ "$LOGIN_STATUS" = "200" ]; then echo "✓ Deployment ready for ${SHORT_VERSION} (attempt $i/20)" exit 0 fi if [ $i -lt 20 ]; then - echo " Attempt $i/20: waiting for deployment... (blog=${BLOG_STATUS:-?}, version=${VERSION_BODY:0:30}...)" + echo " Attempt $i/20: waiting for deployment... (blog=${BLOG_STATUS:-?}, login=${LOGIN_STATUS:-?}, version=${VERSION_BODY:0:30}...)" sleep 3 fi done @@ -72,6 +73,23 @@ jobs: echo "Running E2E tests on Desktop Chrome (production verification)" npx playwright test --project="Desktop Chrome" --reporter=html --reporter=list + - name: API smoke verification + env: + DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }} + E2E_ADMIN_USERNAME: test_admin + E2E_ADMIN_PASSWORD: TestAdmin@123456 + run: | + set -e + TOKEN="$(curl -s -X POST "http://${DEPLOY_HOST}/taxbaik/api/auth/login" -H "Content-Type: application/json" -d "{\"username\":\"${E2E_ADMIN_USERNAME}\",\"password\":\"${E2E_ADMIN_PASSWORD}\"}" | python3 -c 'import sys, json; print(json.load(sys.stdin)["accessToken"])')" + test -n "$TOKEN" + curl -fsS -H "Authorization: Bearer $TOKEN" "http://${DEPLOY_HOST}/taxbaik/api/blog/admin?page=1&pageSize=1" >/dev/null + curl -fsS -H "Authorization: Bearer $TOKEN" "http://${DEPLOY_HOST}/taxbaik/api/faq" >/dev/null + curl -fsS -H "Authorization: Bearer $TOKEN" "http://${DEPLOY_HOST}/taxbaik/api/announcement" >/dev/null + curl -fsS -H "Authorization: Bearer $TOKEN" "http://${DEPLOY_HOST}/taxbaik/api/inquiry?page=1&pageSize=1" >/dev/null + curl -fsS "http://${DEPLOY_HOST}/taxbaik/favicon.svg" >/dev/null + curl -fsS "http://${DEPLOY_HOST}/taxbaik/favicon.ico" >/dev/null + curl -fsS "http://${DEPLOY_HOST}/taxbaik/robots.txt" >/dev/null + - name: Browser E2E summary if: always() run: | diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index 9e6d593..3df5c79 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -1,7 +1,6 @@ name: TaxBaik CI/CD on: - workflow_dispatch: push: branches: - master @@ -33,6 +32,9 @@ jobs: - name: Publish Web run: dotnet publish TaxBaik.Web/ -c Release -o ./publish --no-restore + - name: Publish Proxy + run: dotnet publish TaxBaik.Proxy/ -c Release -o ./publish/proxy --no-restore + - name: Write production secrets run: | set -e @@ -67,6 +69,11 @@ jobs: )' test -s ./publish/appsettings.Production.json || { echo "appsettings.Production.json is empty" >&2; exit 1; } + - name: Verify proxy artifact + run: | + test -s ./publish/proxy/TaxBaik.Proxy.dll || { echo "TaxBaik.Proxy.dll missing" >&2; exit 1; } + test -s ./publish/proxy/TaxBaik.Proxy.runtimeconfig.json || { echo "TaxBaik.Proxy.runtimeconfig.json missing" >&2; exit 1; } + - name: Copy migrations run: cp -r db/migrations ./publish/migrations || true @@ -107,6 +114,7 @@ jobs: - name: Deploy & verify on server run: | set -e + export TAXBAIK_DEPLOY_FROM_CI=1 TIMESTAMP=$(date +%Y%m%d_%H%M%S) COMMIT=$(git rev-parse --short HEAD) DEPLOY_HOST="${{ secrets.DEPLOY_HOST }}" @@ -163,6 +171,8 @@ jobs: echo "--- [2/5] 운영 설정 검증 ---" test -s "\$DEPLOY_DIR/appsettings.Production.json" \ || { echo "FATAL: appsettings.Production.json 없음" >&2; exit 1; } + test -s "\$DEPLOY_DIR/proxy/TaxBaik.Proxy.dll" \ + || { echo "FATAL: TaxBaik.Proxy.dll 없음" >&2; exit 1; } echo "--- [3/4] Green-Blue 배포 실행 ---" chmod +x "\$DEPLOY_DIR/deploy_gb.sh" @@ -190,13 +200,20 @@ jobs: fi echo "✓ [3/4] 버전 정보 확인 완료" - # 검증 3: 관리자 로그인 페이지 + # 검증 4: 5001 프록시 확인 + if ! ss -tlnp | grep -q ':5001 '; then + echo "❌ 5001 프록시가 실행 중이 아님" >&2 + exit 1 + fi + echo "✓ [4/5] 5001 프록시 확인 완료" + + # 검증 5: 관리자 로그인 페이지 LOGIN_STATUS=\$(curl -sf -o /dev/null -w '%{http_code}' http://127.0.0.1:5001/taxbaik/admin/login 2>/dev/null || echo "000") if [ "\$LOGIN_STATUS" != "200" ]; then echo "❌ 관리자 로그인 페이지 로드 실패 (상태: \$LOGIN_STATUS)" >&2 exit 1 fi - echo "✓ [4/4] 관리자 페이지 로드 완료" + echo "✓ [5/5] 관리자 페이지 로드 완료" echo "✓ 서비스 정상 (시도 \$i/\$ATTEMPTS)" # 구 배포 디렉토리 정리 (최근 5개 보존) diff --git a/DEPLOYMENT_GUIDE.md b/DEPLOYMENT_GUIDE.md index e6edf47..f821f0f 100644 --- a/DEPLOYMENT_GUIDE.md +++ b/DEPLOYMENT_GUIDE.md @@ -19,20 +19,30 @@ GRANT ALL PRIVILEGES ON DATABASE taxbaikdb TO taxbaik; ### 2. 환경 변수 설정 -**Web 서비스** (`/etc/systemd/system/taxbaik.service`): +**Web 서비스** (`/etc/systemd/system/taxbaik.service`, 백엔드 전용): ```ini [Service] Environment=ASPNETCORE_ENVIRONMENT=Production -Environment=ASPNETCORE_URLS=http://127.0.0.1:5001 +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 설정 @@ -69,7 +79,7 @@ sudo systemctl reload nginx master 브랜치 push → build → test → publish → restart → health check → Playwright ``` -수동 배포는 비상 롤백 외에는 사용하지 않습니다. 배포 이슈는 Gitea Actions 로그로 해결합니다. +수동 배포는 사용하지 않습니다. `deploy_gb.sh`는 `TAXBAIK_DEPLOY_FROM_CI=1`이 없으면 즉시 종료하므로, 배포는 반드시 Gitea Actions에서만 실행됩니다. ## 마이그레이션 자동 실행 @@ -132,6 +142,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 ``` @@ -143,10 +154,10 @@ sudo systemctl restart taxbaik ssh kjh2064@178.104.200.7 # 서비스 상태 -systemctl status taxbaik +systemctl status taxbaik taxbaik-proxy # 포트 확인 -netstat -tlnp | grep -E '5001' +netstat -tlnp | grep -E '5001|5004' # 프로세스 확인 ps aux | grep TaxBaik @@ -169,9 +180,27 @@ 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` | +| 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' +``` + ## 초기 데이터 ### 관리자 계정 diff --git a/PRODUCTION_CHECKLIST.md b/PRODUCTION_CHECKLIST.md index 3420227..f1589f1 100644 --- a/PRODUCTION_CHECKLIST.md +++ b/PRODUCTION_CHECKLIST.md @@ -48,29 +48,7 @@ ssh kjh2064@178.104.200.7 'bash ~/SERVER_SETUP.sh' # ~/taxbaik_active ``` -### 2단계: 첫 배포 (수동) - -```bash -# 로컬에서 실행 -TIMESTAMP=$(date +%Y%m%d_%H%M%S) - -# SSH 키 설정 (필요시) -export DEPLOY_USER="kjh2064" -export DEPLOY_HOST="178.104.200.7" - -# 배포 -rsync -avz --delete ./publish/ \ - $DEPLOY_USER@$DEPLOY_HOST:~/deployments/taxbaik_${TIMESTAMP}/ - -# 심링크 변경 및 시작 -ssh $DEPLOY_USER@$DEPLOY_HOST << EOF -ln -sfn ~/deployments/taxbaik_${TIMESTAMP} ~/taxbaik_active -sudo systemctl start taxbaik -sudo systemctl status taxbaik -EOF -``` - -### 3단계: Gitea Actions 설정 (선택) +### 2단계: Gitea Actions 설정 **Gitea 저장소 Settings → Secrets 추가**: - `DEPLOY_USER`: `kjh2064` @@ -217,8 +195,8 @@ curl -I -H "Accept-Encoding: gzip" http://178.104.200.7/taxbaik/ | grep -i encod | 증상 | 원인 | 해결 방법 | |------|------|----------| | 404 /taxbaik | Nginx 설정 미적용 | `sudo nginx -t && sudo systemctl reload nginx` | -| 502 Bad Gateway | 앱 미실행 | `sudo systemctl restart taxbaik` | -| 503 Service Unavailable | 앱 충돌 | 로그 확인: `journalctl -u taxbaik -n 50` | +| 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;` 후 재시작 | @@ -230,11 +208,11 @@ curl -I -H "Accept-Encoding: gzip" http://178.104.200.7/taxbaik/ | grep -i encod ### 실시간 모니터링 ```bash -# 터미널 1: 웹 서비스 로그 +# 터미널 1: 백엔드 로그 ssh kjh2064@178.104.200.7 'journalctl -u taxbaik -f' -# 터미널 2: 통합 서비스 로그 -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' @@ -246,13 +224,7 @@ ssh kjh2064@178.104.200.7 'watch -n 1 "ps aux | grep TaxBaik"' ### 정기적 검사 ```bash -# 일일 체크 (cron job) -0 9 * * * /home/kjh2064/health-check.sh - -# 내용: -#!/bin/bash -curl -f http://127.0.0.1:5001/taxbaik || systemctl restart taxbaik -curl -f http://127.0.0.1:5001/taxbaik/admin/login || systemctl restart taxbaik +# 일일 체크는 CI 배포 후 자동 검증으로 대체 ``` --- @@ -268,11 +240,6 @@ git commit -m "기능: 새로운 기능 추가" git push origin master # 2. Gitea Actions가 자동으로 배포 -# 또는 수동 배포: -TIMESTAMP=$(date +%Y%m%d_%H%M%S) -dotnet publish TaxBaik.Web -c Release -o ./publish -rsync -avz ./publish/ kjh2064@178.104.200.7:~/deployments/taxbaik_${TIMESTAMP}/ -ssh kjh2064@178.104.200.7 "ln -sfn ~/deployments/taxbaik_${TIMESTAMP} ~/taxbaik_active && sudo systemctl restart taxbaik" ``` ### 롤백 절차 @@ -284,6 +251,7 @@ 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 ``` diff --git a/README.md b/README.md index 4e99196..27b7ece 100644 --- a/README.md +++ b/README.md @@ -168,7 +168,7 @@ master 브랜치에 푸시하면 파이프라인이 다음 단계를 수행합 - `TAXBAIK_ADMIN_TEST_PASSWORD`: 배포 검증용 관리자 비밀번호 - `Admin__PasswordResetToken`: 관리자 비밀번호 재설정 API용 서버 비밀값 -수동 배포는 비상 롤백 절차 외에는 사용하지 않습니다. 실패 시 [DEPLOYMENT_GUIDE.md](./DEPLOYMENT_GUIDE.md)의 CI 점검 절차를 따릅니다. +배포는 Gitea Actions CI/CD로만 수행합니다. 수동 배포 경로는 CI 하네스로 차단되어 있으며, 실패 시 [DEPLOYMENT_GUIDE.md](./DEPLOYMENT_GUIDE.md)의 CI 점검 절차를 따릅니다. --- diff --git a/TaxBaik.Web/Components/Admin/App.razor b/TaxBaik.Web/Components/Admin/App.razor index 7cca9f5..2749041 100644 --- a/TaxBaik.Web/Components/Admin/App.razor +++ b/TaxBaik.Web/Components/Admin/App.razor @@ -6,6 +6,8 @@