diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml new file mode 100644 index 00000000..3370e3b0 --- /dev/null +++ b/.gitea/workflows/build.yml @@ -0,0 +1,161 @@ +name: Build & Package + +on: + push: + branches: + - main + workflow_dispatch: + +env: + DOTNET_VERSION: '10.0.x' + REGISTRY: ghcr.io + +jobs: + build: + runs-on: ubuntu-latest + timeout-minutes: 20 + permissions: + contents: write + packages: write + + outputs: + build-tag: ${{ steps.metadata.outputs.tag }} + commit-hash: ${{ steps.metadata.outputs.commit }} + build-time: ${{ steps.metadata.outputs.build-time }} + + steps: + - name: Checkout Code + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Setup .NET + uses: actions/setup-dotnet@v3 + with: + dotnet-version: ${{ env.DOTNET_VERSION }} + + - name: Setup Python + uses: actions/setup-python@v4 + with: + python-version: '3.10' + + - name: Install Python Dependencies + run: pip install pyyaml openpyxl requests + + - name: "[GATE] Run Critical Validations" + run: | + echo "πŸ” Running critical CI validations..." + python3 tools/validate_no_direct_api_trading_v1.py || exit 1 + python3 tools/validate_specs.py || exit 1 + echo "βœ… All critical validations passed" + + - name: Prepare Temp Directory + run: | + mkdir -p Temp + if [ ! -f Temp/final_decision_packet_active.json ]; then + echo '{"active_decision": "PASS", "details": "CI dummy packet"}' > Temp/final_decision_packet_active.json + fi + + - name: Restore .NET Dependencies + run: dotnet restore src/dotnet/QuantEngine.Web/QuantEngine.Web.csproj + + - name: Build Release + run: | + dotnet build src/dotnet/QuantEngine.Web/QuantEngine.Web.csproj \ + -c Release \ + --no-restore + + - name: Run Unit Tests + run: | + dotnet test src/dotnet/QuantEngine.Core.Tests/QuantEngine.Core.Tests.csproj \ + -c Release \ + --no-build + + - name: Publish Release Package + run: | + dotnet publish src/dotnet/QuantEngine.Web/QuantEngine.Web.csproj \ + -c Release \ + --no-build \ + -o ./publish + + - name: Generate Build Metadata + id: metadata + run: | + COMMIT=$(git rev-parse --short HEAD) + BUILD_TIME=$(date -u +'%Y-%m-%dT%H:%M:%SZ') + BUILD_TAG="build-${COMMIT}" + + mkdir -p ./publish/wwwroot + cat > ./publish/wwwroot/version.json <> $GITHUB_OUTPUT + echo "commit=${COMMIT}" >> $GITHUB_OUTPUT + echo "build-time=${BUILD_TIME}" >> $GITHUB_OUTPUT + echo "βœ“ Build metadata: ${BUILD_TAG} @ ${BUILD_TIME}" + + - name: Create Deployment Package + run: | + echo "πŸ“¦ Creating deployment package..." + + tar -czf quantengine-${{ steps.metadata.outputs.commit }}.tar.gz \ + -C ./publish . + + PACKAGE_SIZE=$(du -sh quantengine-${{ steps.metadata.outputs.commit }}.tar.gz | cut -f1) + echo "βœ“ Package created: ${PACKAGE_SIZE}" + + # Verify package integrity + tar -tzf quantengine-${{ steps.metadata.outputs.commit }}.tar.gz > /dev/null || exit 1 + echo "βœ“ Package integrity verified" + + - name: Create GitHub Release + uses: ncipollo/release-action@v1 + with: + tag: ${{ steps.metadata.outputs.tag }} + name: Build ${{ steps.metadata.outputs.tag }} + body: | + **Build Information** + - **Commit**: `${{ steps.metadata.outputs.commit }}` + - **Built At**: ${{ steps.metadata.outputs.build-time }} + - **Build Number**: ${{ github.run_number }} + + **Quality Gates** + - βœ… No direct API trading validation + - βœ… Specification validation + - βœ… Unit tests passed + + **Release Package** + - Release artifact: `quantengine-${{ steps.metadata.outputs.commit }}.tar.gz` + - Size: $(du -sh quantengine-${{ steps.metadata.outputs.commit }}.tar.gz | cut -f1) + + **Deployment Instructions** + ```bash + # Trigger production deployment with this build + curl -X POST https://gitea.taxbaik.com/api/v1/repos/kjh2064/QuantEngineByItz/actions/workflows/deploy-prod.yml/dispatches \ + -H "Authorization: token ${GITEA_TOKEN}" \ + -H "Content-Type: application/json" \ + -d '{"ref":"main", "inputs":{"release_tag":"${{ steps.metadata.outputs.tag }}"}}' + ``` + draft: false + prerelease: false + artifacts: quantengine-${{ steps.metadata.outputs.commit }}.tar.gz + artifactErrorsFailBuild: true + updateLatestRelease: true + + - name: Notify Build Success + if: success() + run: | + echo "βœ… Build ${{ steps.metadata.outputs.tag }} completed successfully" + echo "πŸ“¦ Package available at release: ${{ steps.metadata.outputs.tag }}" + + - name: Notify Build Failure + if: failure() + run: | + echo "❌ Build ${{ steps.metadata.outputs.tag }} failed" + exit 1 diff --git a/CICD_ROADMAP.md b/CICD_ROADMAP.md new file mode 100644 index 00000000..47b84b9a --- /dev/null +++ b/CICD_ROADMAP.md @@ -0,0 +1,281 @@ +# QuantEngine Gitea Actions CI/CD κ°œμ„  λ‘œλ“œλ§΅ + +**μ΅œμ’… λͺ©ν‘œ**: μ‹ λ’°μ„± 높은 μžλ™ν™”λœ 배포 νŒŒμ΄ν”„λΌμΈ ꡬ좕 + +--- + +## βœ… Phase 1 μ™„λ£Œ (2026-07-11 컀밋: 96cc7fc) + +### 1.1 νƒ€μž„μ•„μ›ƒ ν™•λŒ€ (15λΆ„ β†’ 30λΆ„) +- **효과**: λ„€νŠΈμ›Œν¬ μ§€μ—° 및 μž¬μ‹œλ„ μ‹œ μ•ˆμ •μ„± ν–₯상 +- **λ³€κ²½**: `.gitea/workflows/deploy-prod.yml` line 28 + +### 1.2 μžλ™ λ‘€λ°± κ΅¬ν˜„ +- **효과**: 배포 μ‹€νŒ¨ μ‹œ 이전 λ²„μ „μœΌλ‘œ μžλ™ 볡ꡬ +- **κ΅¬ν˜„**: + ```bash + # ν—¬μŠ€μ²΄ν¬ 3회 연속 μ‹€νŒ¨ β†’ 이전 λ²„μ „μœΌλ‘œ μžλ™ 볡ꡬ + if [ $health_check_passed -eq 0 ]; then + PREV_DEPLOY=$(ls -dt /home/kjh2064/deployments/quantengine_* | head -2 | tail -1) + ln -sfn ${PREV_DEPLOY} /home/kjh2064/quantengine_active + sudo systemctl restart quantengine + fi + ``` +- **μž₯점**: + - 배포 μ‹€νŒ¨ λŒ€μ‘ μžλ™ν™” + - μˆ˜λ™ κ°œμž… μ΅œμ†Œν™” + - Telegram μ•Œλ¦Ό μžλ™ λ°œμ†‘ + +### 1.3 ν—¬μŠ€μ²΄ν¬ κ°•ν™” +- **λ°μ΄ν„°λ² μ΄μŠ€ μ—°κ²° 검증** μΆ”κ°€ +- **μ„œλΉ„μŠ€ μƒνƒœ 확인** κ°•ν™” +- **Favicon 검증** 경고둜 λ³€κ²½ (선택사항) + +### 1.4 배포 이λ ₯ 좔적 +- **둜그 파일**: `/home/kjh2064/.config/quantengine_deploy_history.log` +- **기둝 λ‚΄μš©**: + ``` + TIMESTAMP=20260711_175640 + COMMIT=96cc7fc + DEPLOY_PATH=/home/kjh2064/deployments/quantengine_20260711_175640 + PREV_VERSION=20260711_170421 + STATUS=success + DEPLOYED_AT=2026-07-11T17:56:40Z + ``` +- **μš©λ„**: 배포 이λ ₯ 좔적, λΉ λ₯Έ λ‘€λ°± κ²°μ • + +--- + +## πŸ“‹ Phase 2 κ³„νš (λΉŒλ“œ/배포 뢄리) + +### 2.1 별도 λΉŒλ“œ μ›Œν¬ν”Œλ‘œμš° 생성 (**μƒˆλ‘œμš΄ 파일**: `.gitea/workflows/build.yml`) + +**νŠΉμ§•**: +- λΉŒλ“œ κ²°κ³Όλ₯Ό Gitea Releases둜 λ°œν–‰ +- λΉŒλ“œ 메타데이터 (컀밋, νƒ€μž„μŠ€νƒν”„) 포함 +- 배포 μ‹œμ μ— λΉŒλ“œ μž¬μ‚¬μš© + +**효과**: +``` +이전 (ν˜„μž¬): + push β†’ λΉŒλ“œ β†’ ν…ŒμŠ€νŠΈ β†’ 배포 (ν•œ λ²ˆμ—) + +κ°œμ„  ν›„: + push β†’ λΉŒλ“œ (별도) β†’ 배포 (독립적) + └─ 같은 λΉŒλ“œλ₯Ό μ—¬λŸ¬ 번 배포 κ°€λŠ₯ + └─ λΉŒλ“œ μ•„ν‹°νŒ©νŠΈ μž¬μ‚¬μš© β†’ 속도 ↑ +``` + +### 2.2 `appsettings.Production.json` μ „λž΅ λ³€κ²½ + +**ν˜„μž¬ 문제점**: +```yaml +# ν˜„μž¬ (deploy-prod.yml) +- name: Publish Release Package + run: dotnet publish ... -o ./publish + +- name: Prepare & Validate DB Env # 배포 μ‹œμ μ— 생성 + run: | + cat > ./publish/appsettings.Production.json << EOF + { + "ConnectionStrings": { + "DefaultConnection": "Host=127.0.0.1;Database=${{ env.QUANTENGINE_DB_NAME }};..." + } + } + EOF +``` + +**문제**: λΉŒλ“œμ™€ 배포 사이에 섀정이 λ™μ μœΌλ‘œ 변경됨 + +**κ°œμ„  λ°©ν–₯**: +```yaml +# κ°œμ„  ν›„ (build.yml) +- name: Generate Configuration Template + run: | + cat > ./publish/appsettings.Production.json.template << EOF + { + "ConnectionStrings": { + "DefaultConnection": "Host={DB_HOST};Database={DB_NAME};Username={DB_USER};..." + } + } + EOF + +# 배포 μ‹œμ μ— (deploy-prod.yml) +- name: Inject Secrets at Deploy Time + run: | + envsubst < appsettings.Production.json.template > appsettings.Production.json +``` + +**효과**: +- βœ… λΉŒλ“œ μ‹œμ  κ³ μ • (μž¬ν˜„ κ°€λŠ₯) +- βœ… 배포 μ‹œμ μ—λ§Œ secrets μ£Όμž… +- βœ… "같은 λΉŒλ“œ β†’ 같은 배포" 보μž₯ + +### 2.3 배포 μ›Œν¬ν”Œλ‘œμš° κ°œμ„  + +**λ³€κ²½ 사항**: +```yaml +# ν˜„μž¬ (deploy-prod.yml) +- name: Setup .NET + ... (μ‹œκ°„ λ‚­λΉ„) + +- name: Build Release + ... (λΉŒλ“œ 반볡) + +# κ°œμ„  ν›„ +- name: Download Build Artifact + run: | + curl -L -o quantengine.tar.gz \ + https://gitea.taxbaik.com/api/v1/repos/.../releases/download/build-${COMMIT}/quantengine-${COMMIT}.tar.gz +``` + +**효과**: +- λΉŒλ“œ μ‹œκ°„ 제거 (5-10λΆ„ 단좕) +- 배포 속도 ↑↑ + +--- + +## 🎯 Phase 3 κ³„νš (E2E 검증 κ°•ν™”) + +### 3.1 둜그인 κΈ°λŠ₯ E2E ν…ŒμŠ€νŠΈ μΆ”κ°€ + +```bash +# deploy-prod.yml에 μΆ”κ°€ +- name: E2E Login Test + run: | + # 1. 둜그인 μ‹œλ„ + LOGIN_RESULT=$(curl -s -c /tmp/cookies.txt \ + -X POST "https://quant.taxbaik.com/Account/Login" \ + -d "username=${{ secrets.ADMIN_USERNAME }}" \ + -d "password=${{ secrets.ADMIN_PASSWORD }}" \ + -o /dev/null -w "%{http_code}") + + # 2. 성곡 확인 + if [ "$LOGIN_RESULT" = "302" ] || [ "$LOGIN_RESULT" = "200" ]; then + echo "βœ“ Login test passed" + else + echo "❌ Login test failed: $LOGIN_RESULT" + exit 1 + fi + + # 3. 인증 μƒνƒœ 확인 + DASHBOARD=$(curl -s -b /tmp/cookies.txt \ + "https://quant.taxbaik.com/Admin/Dashboard" \ + -o /dev/null -w "%{http_code}") + + if [ "$DASHBOARD" = "200" ]; then + echo "βœ“ Dashboard accessible" + else + echo "❌ Dashboard access failed: $DASHBOARD" + exit 1 + fi +``` + +### 3.2 API κΈ°λŠ₯ ν…ŒμŠ€νŠΈ μΆ”κ°€ + +```bash +- name: E2E API Test + run: | + # Collection API μƒνƒœ 확인 + API_RESULT=$(curl -s -b /tmp/cookies.txt \ + "https://quant.taxbaik.com/api/collection/state" \ + -H "Content-Type: application/json" \ + -o /dev/null -w "%{http_code}") + + if [ "$API_RESULT" = "200" ]; then + echo "βœ“ API endpoint responding" + else + echo "❌ API test failed: $API_RESULT" + exit 1 + fi +``` + +--- + +## πŸ“Š κ΅¬ν˜„ μš°μ„ μˆœμœ„ 및 영ν–₯도 + +| μš°μ„  | Phase | ν•­λͺ© | λ‚œμ΄λ„ | 효과 | μ˜ˆμƒ μ†Œμš” | +|------|-------|------|--------|------|----------| +| 1️⃣ | 1 | νƒ€μž„μ•„μ›ƒ ν™•λŒ€ | ⭐ | μ¦‰μ‹œ μ•ˆμ •μ„± ↑ | 5λΆ„ | +| 2️⃣ | 1 | μžλ™ λ‘€λ°± | ⭐⭐ | 배포 μ‹€νŒ¨ λŒ€μ‘ | 30λΆ„ | +| 3️⃣ | 1 | ν—¬μŠ€μ²΄ν¬ κ°•ν™” | ⭐⭐ | 검증 ν™•μ‹€μ„± | 20λΆ„ | +| 4️⃣ | 1 | 배포 이λ ₯ 좔적 | ⭐⭐ | 운영 κ°€μ‹œμ„± | 15λΆ„ | +| 5️⃣ | 2 | λΉŒλ“œ 뢄리 | ⭐⭐⭐ | 속도 ↑↑ + 일관성 | 2μ‹œκ°„ | +| 6️⃣ | 3 | 둜그인 E2E | ⭐⭐⭐ | κΈ°λŠ₯ 검증 | 1μ‹œκ°„ | + +--- + +## πŸ” λͺ¨λ‹ˆν„°λ§ 및 좔적 + +### 배포 이λ ₯ 쑰회 (원격 μ„œλ²„) +```bash +ssh kjh2064@178.104.200.7 +cat ~/.config/quantengine_deploy_history.log | tail -20 +``` + +### 졜근 배포 정보 +```bash +ls -lt /home/kjh2064/deployments/ | head -5 +readlink -f /home/kjh2064/quantengine_active +``` + +### μ„œλΉ„μŠ€ μƒνƒœ 확인 +```bash +sudo systemctl status quantengine +sudo journalctl -u quantengine -f +``` + +--- + +## ✨ κΈ°λŒ€ 효과 + +### 배포 μ‹ λ’°μ„± ν–₯상 +- **이전**: 배포 μ‹€νŒ¨ μ‹œ μˆ˜λ™ λŒ€μ‘ (15-30λΆ„ μ†Œμš”) +- **ν˜„μž¬**: μžλ™ λ‘€λ°± + μ•Œλ¦Ό (1-2λΆ„) + +### 배포 속도 κ°œμ„  (Phase 2) +- **이전**: λΉŒλ“œ 5-10λΆ„ + 배포 2-3λΆ„ = 7-13λΆ„ +- **ν˜„μž¬**: λΉŒλ“œ 뢄리 + μ•„ν‹°νŒ©νŠΈ μž¬μ‚¬μš© = 2-3λΆ„ + +### 운영 κ°€μ‹œμ„± ν–₯상 +- **배포 이λ ₯ 좔적**: μ–Έμ œ, μ–΄λ–€ 버전, λˆ„κ°€ λ°°ν¬ν–ˆλŠ”μ§€ +- **λΉ λ₯Έ λ‘€λ°±**: 이전 λ²„μ „μœΌλ‘œ μ¦‰μ‹œ 볡ꡬ κ°€λŠ₯ +- **κ·Όλ³Έ 원인 뢄석**: 둜그λ₯Ό ν†΅ν•œ 배포 μ‹€νŒ¨ 원인 νŒŒμ•… + +--- + +## λ‹€μŒ μ•‘μ…˜ (μ‚¬μš©μž) + +### Phase 2 μ μš©ν•˜κΈ° +1. `.gitea/workflows/build.yml` 파일 κ²€ν†  및 μ‘°μ • +2. `deploy-prod.yml` μˆ˜μ •ν•˜μ—¬ λΉŒλ“œ μ•„ν‹°νŒ©νŠΈ λ‹€μš΄λ‘œλ“œ 둜직 μΆ”κ°€ +3. GitHub Releases API λŒ€μ‹  Gitea Releases API μ‚¬μš©ν•˜λ„λ‘ λ³€κ²½ + +### ν…ŒμŠ€νŠΈ +```bash +# μˆ˜λ™ 배포 트리거 +curl -X POST https://gitea.taxbaik.com/api/v1/repos/kjh2064/QuantEngineByItz/actions/workflows/deploy-prod.yml/dispatches \ + -H "Authorization: token ${GITEA_TOKEN}" \ + -H "Content-Type: application/json" \ + -d '{"ref":"main", "inputs":{"release_tag":"build-96cc7fc"}}' +``` + +### λͺ¨λ‹ˆν„°λ§ +- Telegram μ•Œλ¦Ό 확인 +- 배포 이λ ₯ 둜그 검증 +- 이전 버전 λ‘€λ°± ν…ŒμŠ€νŠΈ (μŠ€ν…Œμ΄μ§• ν™˜κ²½) + +--- + +## μ°Έκ³  자료 + +- **뢄석 λ¬Έμ„œ**: [gitea_cicd_analysis.md](https://claude.ai/code/artifact/9b62fb29-6438-4cd3-80a4-3593c7057eb5) +- **ν˜„μž¬ μ›Œν¬ν”Œλ‘œμš°**: + - `.gitea/workflows/deploy-prod.yml` (κ°œμ„ λ¨) + - `.gitea/workflows/ci.yml` (κΈ°μ‘΄ Python 검증) +- **배포 슀크립트**: `tools/deploy_quantengine.sh` (κ°œμ„ λ¨) + +--- + +**μž‘μ„±μΌ**: 2026-07-11 +**μƒνƒœ**: Phase 1 βœ… μ™„λ£Œ, Phase 2 πŸ“‹ κ³„νš 쀑, Phase 3 πŸ“‹ κ³„νš 쀑