fix: unify TaxBaik deployment around CI
TaxBaik CI/CD / build-and-deploy (push) Successful in 41s

This commit is contained in:
2026-06-27 01:34:17 +09:00
parent 3c36554164
commit 1d7dd71011
16 changed files with 130 additions and 221 deletions
-1
View File
@@ -1 +0,0 @@
{"sessionId":"c3cb93c0-7adf-4d3a-817d-6c01e0e0f09f","pid":26816,"acquiredAt":1782481349474}
+12 -6
View File
@@ -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
+22 -54
View File
@@ -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
View File
@@ -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
View File
@@ -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
+11 -13
View File
@@ -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
View File
@@ -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;">
+6
View File
@@ -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);
-17
View File
@@ -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"
+3 -14
View File
@@ -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;
}
-26
View File
@@ -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