fix: favicon and ci deployment checks
TaxBaik CI/CD / build-and-deploy (push) Failing after 1m37s

This commit is contained in:
2026-07-01 12:58:21 +09:00
parent 2a046d0393
commit a60451b95f
15 changed files with 208 additions and 74 deletions
+20 -2
View File
@@ -49,12 +49,13 @@ jobs:
# Suppress stderr and allow failures to handle transition/down periods cleanly # 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)" 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)" 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)" echo "✓ Deployment ready for ${SHORT_VERSION} (attempt $i/20)"
exit 0 exit 0
fi fi
if [ $i -lt 20 ]; then 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 sleep 3
fi fi
done done
@@ -72,6 +73,23 @@ jobs:
echo "Running E2E tests on Desktop Chrome (production verification)" echo "Running E2E tests on Desktop Chrome (production verification)"
npx playwright test --project="Desktop Chrome" --reporter=html --reporter=list 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 - name: Browser E2E summary
if: always() if: always()
run: | run: |
+20 -3
View File
@@ -1,7 +1,6 @@
name: TaxBaik CI/CD name: TaxBaik CI/CD
on: on:
workflow_dispatch:
push: push:
branches: branches:
- master - master
@@ -33,6 +32,9 @@ jobs:
- name: Publish Web - name: Publish Web
run: dotnet publish TaxBaik.Web/ -c Release -o ./publish --no-restore 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 - name: Write production secrets
run: | run: |
set -e set -e
@@ -67,6 +69,11 @@ jobs:
)' )'
test -s ./publish/appsettings.Production.json || { echo "appsettings.Production.json is empty" >&2; exit 1; } 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 - name: Copy migrations
run: cp -r db/migrations ./publish/migrations || true run: cp -r db/migrations ./publish/migrations || true
@@ -107,6 +114,7 @@ jobs:
- name: Deploy & verify on server - name: Deploy & verify on server
run: | run: |
set -e set -e
export TAXBAIK_DEPLOY_FROM_CI=1
TIMESTAMP=$(date +%Y%m%d_%H%M%S) TIMESTAMP=$(date +%Y%m%d_%H%M%S)
COMMIT=$(git rev-parse --short HEAD) COMMIT=$(git rev-parse --short HEAD)
DEPLOY_HOST="${{ secrets.DEPLOY_HOST }}" DEPLOY_HOST="${{ secrets.DEPLOY_HOST }}"
@@ -163,6 +171,8 @@ jobs:
echo "--- [2/5] 운영 설정 검증 ---" echo "--- [2/5] 운영 설정 검증 ---"
test -s "\$DEPLOY_DIR/appsettings.Production.json" \ test -s "\$DEPLOY_DIR/appsettings.Production.json" \
|| { echo "FATAL: appsettings.Production.json 없음" >&2; exit 1; } || { 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 배포 실행 ---" echo "--- [3/4] Green-Blue 배포 실행 ---"
chmod +x "\$DEPLOY_DIR/deploy_gb.sh" chmod +x "\$DEPLOY_DIR/deploy_gb.sh"
@@ -190,13 +200,20 @@ jobs:
fi fi
echo "✓ [3/4] 버전 정보 확인 완료" 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") 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 if [ "\$LOGIN_STATUS" != "200" ]; then
echo "❌ 관리자 로그인 페이지 로드 실패 (상태: \$LOGIN_STATUS)" >&2 echo "❌ 관리자 로그인 페이지 로드 실패 (상태: \$LOGIN_STATUS)" >&2
exit 1 exit 1
fi fi
echo "✓ [4/4] 관리자 페이지 로드 완료" echo "✓ [5/5] 관리자 페이지 로드 완료"
echo "✓ 서비스 정상 (시도 \$i/\$ATTEMPTS)" echo "✓ 서비스 정상 (시도 \$i/\$ATTEMPTS)"
# 구 배포 디렉토리 정리 (최근 5개 보존) # 구 배포 디렉토리 정리 (최근 5개 보존)
+35 -6
View File
@@ -19,20 +19,30 @@ GRANT ALL PRIVILEGES ON DATABASE taxbaikdb TO taxbaik;
### 2. 환경 변수 설정 ### 2. 환경 변수 설정
**Web 서비스** (`/etc/systemd/system/taxbaik.service`): **Web 서비스** (`/etc/systemd/system/taxbaik.service`, 백엔드 전용):
```ini ```ini
[Service] [Service]
Environment=ASPNETCORE_ENVIRONMENT=Production 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 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 서비스 파일 설치 ### 3. systemd 서비스 파일 설치
```bash ```bash
sudo cp deploy/taxbaik.service /etc/systemd/system/ sudo cp deploy/taxbaik.service /etc/systemd/system/
sudo cp deploy/taxbaik-proxy.service /etc/systemd/system/
sudo systemctl daemon-reload sudo systemctl daemon-reload
sudo systemctl enable taxbaik sudo systemctl enable taxbaik
sudo systemctl enable taxbaik-proxy
``` ```
### 4. Nginx 설정 ### 4. Nginx 설정
@@ -69,7 +79,7 @@ sudo systemctl reload nginx
master 브랜치 push → build → test → publish → restart → health check → Playwright 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) # 심링크 변경 (예: 이전 버전이 taxbaik_20260626_140000)
ln -sfn ~/deployments/taxbaik_20260626_140000 ~/taxbaik_active ln -sfn ~/deployments/taxbaik_20260626_140000 ~/taxbaik_active
sudo systemctl restart taxbaik-proxy
sudo systemctl restart taxbaik sudo systemctl restart taxbaik
``` ```
@@ -143,10 +154,10 @@ sudo systemctl restart taxbaik
ssh kjh2064@178.104.200.7 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 ps aux | grep TaxBaik
@@ -169,9 +180,27 @@ journalctl -u taxbaik -f
| 404 /taxbaik | Nginx 설정 미적용 | `sudo nginx -t && sudo systemctl reload nginx` | | 404 /taxbaik | Nginx 설정 미적용 | `sudo nginx -t && sudo systemctl reload nginx` |
| Blazor WebSocket 안 됨 | `/taxbaik` location에 `proxy_http_version 1.1`, `Upgrade`, `Connection \"Upgrade\"` 헤더가 모두 있는지 확인 | | Blazor WebSocket 안 됨 | `/taxbaik` location에 `proxy_http_version 1.1`, `Upgrade`, `Connection \"Upgrade\"` 헤더가 모두 있는지 확인 |
| DB 연결 오류 | 환경 변수 미설정 | systemd service 파일의 ConnectionStrings__Default 확인 | | 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;` | | 마이그레이션 실패 | 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'
```
## 초기 데이터 ## 초기 데이터
### 관리자 계정 ### 관리자 계정
+8 -40
View File
@@ -48,29 +48,7 @@ ssh kjh2064@178.104.200.7 'bash ~/SERVER_SETUP.sh'
# ~/taxbaik_active # ~/taxbaik_active
``` ```
### 2단계: 첫 배포 (수동) ### 2단계: Gitea Actions 설정
```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 설정 (선택)
**Gitea 저장소 Settings → Secrets 추가**: **Gitea 저장소 Settings → Secrets 추가**:
- `DEPLOY_USER`: `kjh2064` - `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` | | 404 /taxbaik | Nginx 설정 미적용 | `sudo nginx -t && sudo systemctl reload nginx` |
| 502 Bad Gateway | 미실행 | `sudo systemctl restart taxbaik` | | 502 Bad Gateway | 프록시 또는 백엔드 미실행 | `sudo systemctl restart taxbaik-proxy taxbaik` |
| 503 Service Unavailable | 앱 충돌 | 로그 확인: `journalctl -u taxbaik -n 50` | | 503 Service Unavailable | 백엔드 충돌 또는 비밀값 누락 | 로그 확인: `journalctl -u taxbaik -n 50` |
| DB 연결 오류 | 환경 변수 미설정 | systemd 파일의 ConnectionStrings__Default 확인 | | DB 연결 오류 | 환경 변수 미설정 | systemd 파일의 ConnectionStrings__Default 확인 |
| HTTPS 오류 | SSL 미구성 | 개발 환경에서는 HTTP 사용 (IP 기반) | | HTTPS 오류 | SSL 미구성 | 개발 환경에서는 HTTP 사용 (IP 기반) |
| 마이그레이션 실패 | 테이블 존재 | `DROP DATABASE taxbaikdb;` 후 재시작 | | 마이그레이션 실패 | 테이블 존재 | `DROP DATABASE taxbaikdb;` 후 재시작 |
@@ -230,11 +208,11 @@ curl -I -H "Accept-Encoding: gzip" http://178.104.200.7/taxbaik/ | grep -i encod
### 실시간 모니터링 ### 실시간 모니터링
```bash ```bash
# 터미널 1: 웹 서비스 로그 # 터미널 1: 백엔드 로그
ssh kjh2064@178.104.200.7 'journalctl -u taxbaik -f' ssh kjh2064@178.104.200.7 'journalctl -u taxbaik -f'
# 터미널 2: 통합 서비스 로그 # 터미널 2: 프록시 로그
ssh kjh2064@178.104.200.7 'journalctl -u taxbaik -f' ssh kjh2064@178.104.200.7 'journalctl -u taxbaik-proxy -f'
# 터미널 3: Nginx 로그 # 터미널 3: Nginx 로그
ssh kjh2064@178.104.200.7 'sudo tail -f /var/log/nginx/access.log | grep taxbaik' 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 ```bash
# 일일 체크 (cron job) # 일일 체크는 CI 배포 후 자동 검증으로 대체
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
``` ```
--- ---
@@ -268,11 +240,6 @@ git commit -m "기능: 새로운 기능 추가"
git push origin master git push origin master
# 2. Gitea Actions가 자동으로 배포 # 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) # 롤백 (예: 이전 버전이 taxbaik_20260625_100000)
ssh kjh2064@178.104.200.7 << EOF ssh kjh2064@178.104.200.7 << EOF
ln -sfn ~/deployments/taxbaik_20260625_100000 ~/taxbaik_active ln -sfn ~/deployments/taxbaik_20260625_100000 ~/taxbaik_active
sudo systemctl restart taxbaik-proxy
sudo systemctl restart taxbaik sudo systemctl restart taxbaik
EOF EOF
``` ```
+1 -1
View File
@@ -168,7 +168,7 @@ master 브랜치에 푸시하면 파이프라인이 다음 단계를 수행합
- `TAXBAIK_ADMIN_TEST_PASSWORD`: 배포 검증용 관리자 비밀번호 - `TAXBAIK_ADMIN_TEST_PASSWORD`: 배포 검증용 관리자 비밀번호
- `Admin__PasswordResetToken`: 관리자 비밀번호 재설정 API용 서버 비밀값 - `Admin__PasswordResetToken`: 관리자 비밀번호 재설정 API용 서버 비밀값
수동 배포는 비상 롤백 절차 외에는 사용하지 않습니다. 실패 시 [DEPLOYMENT_GUIDE.md](./DEPLOYMENT_GUIDE.md)의 CI 점검 절차를 따릅니다. 배포는 Gitea Actions CI/CD로만 수행합니다. 수동 배포 경로는 CI 하네스로 차단되어 있으며, 실패 시 [DEPLOYMENT_GUIDE.md](./DEPLOYMENT_GUIDE.md)의 CI 점검 절차를 따릅니다.
--- ---
+2
View File
@@ -6,6 +6,8 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>백원숙 세무회계 - 관리자</title> <title>백원숙 세무회계 - 관리자</title>
<base href="/taxbaik/" /> <base href="/taxbaik/" />
<link rel="icon" type="image/svg+xml" href="/taxbaik/favicon.svg" />
<link rel="alternate icon" href="/taxbaik/favicon.ico" />
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@400;500;700&display=swap" rel="stylesheet" /> <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@400;500;700&display=swap" rel="stylesheet" />
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" /> <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />
<link href="_content/MudBlazor/MudBlazor.min.css" rel="stylesheet" /> <link href="_content/MudBlazor/MudBlazor.min.css" rel="stylesheet" />
+2
View File
@@ -26,6 +26,8 @@
<meta name="robots" content="index, follow" /> <meta name="robots" content="index, follow" />
<meta name="theme-color" content="#C89D6E" /> <meta name="theme-color" content="#C89D6E" />
<link rel="icon" type="image/svg+xml" href="/taxbaik/favicon.svg" />
<link rel="alternate icon" href="/taxbaik/favicon.ico" />
<link rel="preconnect" href="https://fonts.googleapis.com" /> <link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link rel="dns-prefetch" href="https://cdn.jsdelivr.net" /> <link rel="dns-prefetch" href="https://cdn.jsdelivr.net" />
+26 -12
View File
@@ -210,58 +210,58 @@ var apiBaseUrl = builder.Configuration["ApiClient:BaseUrl"]
builder.Services.AddHttpClient<IAdminDashboardClient, AdminDashboardClient>(client => builder.Services.AddHttpClient<IAdminDashboardClient, AdminDashboardClient>(client =>
{ {
client.BaseAddress = new Uri(apiBaseUrl); client.BaseAddress = new Uri(apiBaseUrl);
}); }).AddHttpMessageHandler<TokenRefreshHandler>();
builder.Services.AddHttpClient<IInquiryBrowserClient, InquiryBrowserClient>(client => builder.Services.AddHttpClient<IInquiryBrowserClient, InquiryBrowserClient>(client =>
{ {
client.BaseAddress = new Uri(apiBaseUrl); client.BaseAddress = new Uri(apiBaseUrl);
}); }).AddHttpMessageHandler<TokenRefreshHandler>();
builder.Services.AddHttpClient<IClientBrowserClient, ClientBrowserClient>(client => builder.Services.AddHttpClient<IClientBrowserClient, ClientBrowserClient>(client =>
{ {
client.BaseAddress = new Uri(apiBaseUrl); client.BaseAddress = new Uri(apiBaseUrl);
}); }).AddHttpMessageHandler<TokenRefreshHandler>();
builder.Services.AddHttpClient<ITaxFilingBrowserClient, TaxFilingBrowserClient>(client => builder.Services.AddHttpClient<ITaxFilingBrowserClient, TaxFilingBrowserClient>(client =>
{ {
client.BaseAddress = new Uri(apiBaseUrl); client.BaseAddress = new Uri(apiBaseUrl);
}); }).AddHttpMessageHandler<TokenRefreshHandler>();
builder.Services.AddHttpClient<IFaqBrowserClient, FaqBrowserClient>(client => builder.Services.AddHttpClient<IFaqBrowserClient, FaqBrowserClient>(client =>
{ {
client.BaseAddress = new Uri(apiBaseUrl); client.BaseAddress = new Uri(apiBaseUrl);
}); }).AddHttpMessageHandler<TokenRefreshHandler>();
builder.Services.AddHttpClient<IAnnouncementBrowserClient, AnnouncementBrowserClient>(client => builder.Services.AddHttpClient<IAnnouncementBrowserClient, AnnouncementBrowserClient>(client =>
{ {
client.BaseAddress = new Uri(apiBaseUrl); client.BaseAddress = new Uri(apiBaseUrl);
}); }).AddHttpMessageHandler<TokenRefreshHandler>();
// Phase 5: Tax Accounting & CRM Browser Clients // Phase 5: Tax Accounting & CRM Browser Clients
builder.Services.AddHttpClient<ITaxProfileBrowserClient, TaxProfileBrowserClient>(client => builder.Services.AddHttpClient<ITaxProfileBrowserClient, TaxProfileBrowserClient>(client =>
{ {
client.BaseAddress = new Uri(apiBaseUrl); client.BaseAddress = new Uri(apiBaseUrl);
}); }).AddHttpMessageHandler<TokenRefreshHandler>();
builder.Services.AddHttpClient<ITaxFilingScheduleBrowserClient, TaxFilingScheduleBrowserClient>(client => builder.Services.AddHttpClient<ITaxFilingScheduleBrowserClient, TaxFilingScheduleBrowserClient>(client =>
{ {
client.BaseAddress = new Uri(apiBaseUrl); client.BaseAddress = new Uri(apiBaseUrl);
}); }).AddHttpMessageHandler<TokenRefreshHandler>();
builder.Services.AddHttpClient<IConsultingActivityBrowserClient, ConsultingActivityBrowserClient>(client => builder.Services.AddHttpClient<IConsultingActivityBrowserClient, ConsultingActivityBrowserClient>(client =>
{ {
client.BaseAddress = new Uri(apiBaseUrl); client.BaseAddress = new Uri(apiBaseUrl);
}); }).AddHttpMessageHandler<TokenRefreshHandler>();
builder.Services.AddHttpClient<IContractBrowserClient, ContractBrowserClient>(client => builder.Services.AddHttpClient<IContractBrowserClient, ContractBrowserClient>(client =>
{ {
client.BaseAddress = new Uri(apiBaseUrl); client.BaseAddress = new Uri(apiBaseUrl);
}); }).AddHttpMessageHandler<TokenRefreshHandler>();
builder.Services.AddHttpClient<IRevenueTrackingBrowserClient, RevenueTrackingBrowserClient>(client => builder.Services.AddHttpClient<IRevenueTrackingBrowserClient, RevenueTrackingBrowserClient>(client =>
{ {
client.BaseAddress = new Uri(apiBaseUrl); client.BaseAddress = new Uri(apiBaseUrl);
}); }).AddHttpMessageHandler<TokenRefreshHandler>();
builder.Services.AddHttpClient<ICommonCodeBrowserClient, CommonCodeBrowserClient>(client => builder.Services.AddHttpClient<ICommonCodeBrowserClient, CommonCodeBrowserClient>(client =>
{ {
client.BaseAddress = new Uri(apiBaseUrl); client.BaseAddress = new Uri(apiBaseUrl);
}); }).AddHttpMessageHandler<TokenRefreshHandler>();
// UI & 캐시 (MudBlazor Theme Customization) // UI & 캐시 (MudBlazor Theme Customization)
builder.Services.AddMudServices(config => builder.Services.AddMudServices(config =>
@@ -315,6 +315,20 @@ app.UseForwardedHeaders(new ForwardedHeadersOptions
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
}); });
app.Use(async (context, next) =>
{
var path = context.Request.Path.Value ?? string.Empty;
if (path.Equals("/favicon.ico", StringComparison.OrdinalIgnoreCase) ||
path.Equals("/taxbaik/favicon.ico", StringComparison.OrdinalIgnoreCase))
{
context.Response.ContentType = "image/svg+xml";
await context.Response.SendFileAsync(Path.Combine(app.Environment.WebRootPath ?? "wwwroot", "favicon.svg"));
return;
}
await next();
});
// Run migrations on startup (non-blocking for development) // Run migrations on startup (non-blocking for development)
try try
{ {
@@ -15,18 +15,29 @@ public class TelegramReportBackgroundService(
{ {
using var timer = new PeriodicTimer(TimeSpan.FromMinutes(30)); using var timer = new PeriodicTimer(TimeSpan.FromMinutes(30));
while (await timer.WaitForNextTickAsync(stoppingToken)) try
{ {
try while (await timer.WaitForNextTickAsync(stoppingToken))
{ {
var now = TimeZoneInfo.ConvertTime(DateTimeOffset.UtcNow, KoreaTimeZone); try
await TrySendReportsAsync(now, stoppingToken); {
} var now = TimeZoneInfo.ConvertTime(DateTimeOffset.UtcNow, KoreaTimeZone);
catch (Exception ex) await TrySendReportsAsync(now, stoppingToken);
{ }
logger.LogError(ex, "Telegram report background loop failed"); catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested)
{
break;
}
catch (Exception ex)
{
logger.LogError(ex, "Telegram report background loop failed");
}
} }
} }
catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested)
{
// Normal shutdown path.
}
} }
private async Task TrySendReportsAsync(DateTimeOffset nowKst, CancellationToken ct) private async Task TrySendReportsAsync(DateTimeOffset nowKst, CancellationToken ct)
+12
View File
@@ -0,0 +1,12 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-label="TaxBaik">
<defs>
<linearGradient id="g" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#1f3c88"/>
<stop offset="100%" stop-color="#d7a86e"/>
</linearGradient>
</defs>
<rect width="64" height="64" rx="16" fill="url(#g)"/>
<path d="M18 24h28v6H18zM22 32h20v6H22zM26 40h12v6H26z" fill="#fff" opacity="0.95"/>
<path d="M16 18h32v2H16z" fill="#ffffff" opacity="0.35"/>
<circle cx="46" cy="18" r="5" fill="#fff" opacity="0.9"/>
</svg>

After

Width:  |  Height:  |  Size: 564 B

+3
View File
@@ -11,6 +11,9 @@ window.taxbaikAdminSession = {
clearAuthToken: function () { clearAuthToken: function () {
try { try {
localStorage.removeItem('accessToken');
localStorage.removeItem('refreshToken');
localStorage.removeItem('tokenExpiry');
localStorage.removeItem('auth_token'); localStorage.removeItem('auth_token');
} catch { } catch {
// Ignore storage errors; redirect still recovers the session. // Ignore storage errors; redirect still recovers the session.
+5
View File
@@ -1,6 +1,11 @@
#!/bin/bash #!/bin/bash
set -e set -e
if [ "${TAXBAIK_DEPLOY_FROM_CI:-}" != "1" ]; then
echo "❌ This deployment script may only be run from CI." >&2
exit 1
fi
DEPLOY_HOME="/home/kjh2064" DEPLOY_HOME="/home/kjh2064"
WEB_TIMESTAMP=$(date +%Y%m%d_%H%M%S) WEB_TIMESTAMP=$(date +%Y%m%d_%H%M%S)
+22
View File
@@ -0,0 +1,22 @@
[Unit]
Description=TaxBaik Local TCP Proxy (5001 -> active blue/green port)
After=network.target
[Service]
Type=simple
User=kjh2064
WorkingDirectory=/home/kjh2064/taxbaik_active
ExecStart=/usr/bin/dotnet TaxBaik.Proxy.dll
Restart=always
RestartSec=3
# Proxy는 백엔드 포트(5003/5004) 전환 중에도 살아 있어야 한다.
TimeoutStopSec=15
KillMode=mixed
KillSignal=SIGTERM
SyslogIdentifier=taxbaik-proxy
Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false
[Install]
WantedBy=multi-user.target
+2 -2
View File
@@ -1,5 +1,5 @@
[Unit] [Unit]
Description=TaxBaik Website and Admin (.NET 10) Description=TaxBaik Backend App (.NET 10)
After=network.target After=network.target
[Service] [Service]
@@ -17,7 +17,7 @@ KillSignal=SIGTERM
SyslogIdentifier=taxbaik SyslogIdentifier=taxbaik
Environment=ASPNETCORE_ENVIRONMENT=Production Environment=ASPNETCORE_ENVIRONMENT=Production
Environment=ASPNETCORE_URLS=http://127.0.0.1:5001 Environment=ASPNETCORE_URLS=http://127.0.0.1:5004
Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false
# 아래 줄은 서버에서 직접 편집 (git에 커밋하지 않음) # 아래 줄은 서버에서 직접 편집 (git에 커밋하지 않음)
# Environment=ConnectionStrings__Default=Host=localhost;Database=taxbaikdb;Username=taxbaik;Password=CHANGE_ME # Environment=ConnectionStrings__Default=Host=localhost;Database=taxbaikdb;Username=taxbaik;Password=CHANGE_ME
+31
View File
@@ -7,6 +7,37 @@ TIMESTAMP=$(date +%Y%m%d_%H%M%S)
echo "===== 🚀 TaxBaik Green/Blue Deployment Script =====" echo "===== 🚀 TaxBaik Green/Blue Deployment Script ====="
if [ "${TAXBAIK_DEPLOY_FROM_CI:-}" != "1" ]; then
echo "❌ This deployment script may only be run from CI." >&2
exit 1
fi
if [ ! -s "$DEPLOY_HOME/taxbaik_active/appsettings.Production.json" ]; then
echo "❌ Missing production settings: $DEPLOY_HOME/taxbaik_active/appsettings.Production.json" >&2
exit 1
fi
if [ ! -s "$DEPLOY_HOME/taxbaik_active/proxy/TaxBaik.Proxy.dll" ]; then
echo "❌ Missing proxy artifact: $DEPLOY_HOME/taxbaik_active/proxy/TaxBaik.Proxy.dll" >&2
exit 1
fi
# 0. Ensure the local TCP proxy exists and is running.
# Nginx and external traffic always enter through 127.0.0.1:5001.
if ! ss -tln | grep -q ':5001 '; then
if [ -f "$DEPLOY_HOME/taxbaik_active/proxy/TaxBaik.Proxy.dll" ]; then
echo "=== Starting proxy on 127.0.0.1:5001 ==="
cd "$DEPLOY_HOME/taxbaik_active/proxy"
nohup /usr/bin/dotnet TaxBaik.Proxy.dll > "$DEPLOY_HOME/taxbaik_proxy.log" 2>&1 &
sleep 2
fi
fi
if ! ss -tln | grep -q ':5001 '; then
echo "❌ Proxy on 127.0.0.1:5001 is not running. Abort deploy." >&2
exit 1
fi
# 1. Determine active port # 1. Determine active port
ACTIVE_PORT=5003 ACTIVE_PORT=5003
if [ -f "$PORT_FILE" ]; then if [ -f "$PORT_FILE" ]; then