diff --git a/.gitea/workflows/deploy-prod.yml b/.gitea/workflows/deploy-prod.yml index 2c5aaab..29e5b8d 100644 --- a/.gitea/workflows/deploy-prod.yml +++ b/.gitea/workflows/deploy-prod.yml @@ -2,193 +2,190 @@ name: Deploy to Production on: push: - branches: [ main ] + branches: + - main workflow_dispatch: +concurrency: + group: deploy-prod-main + cancel-in-progress: true + env: - DEPLOY_HOST: 172.17.0.1 + DEPLOY_HOST: 178.104.200.7 DEPLOY_USER: kjh2064 - DEPLOY_PATH: /home/kjh2064/quantengine_active SERVICE_NAME: quantengine DOTNET_VERSION: '10.0.x' - TELEGRAM_BOT_TOKEN_DEFAULT: "8734507814:AAFyacLMai8GB4K-hQ_Nd3t3D01A-h1ZdV0" + TELEGRAM_BOT_TOKEN_DEFAULT: "8734507814:AAFyacLMai8GB4K-hQ_Nd3t3D01A-H1ZdV0" TELEGRAM_CHAT_ID_DEFAULT: "-5460205872" jobs: build-and-deploy: name: Build & Deploy to Production runs-on: ubuntu-latest + timeout-minutes: 15 steps: - - name: Checkout Code - uses: actions/checkout@v3 - with: - fetch-depth: 0 + - name: Checkout Code + uses: actions/checkout@v3 - - name: Setup .NET - uses: actions/setup-dotnet@v3 - with: - dotnet-version: ${{ env.DOTNET_VERSION }} + - 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: Setup Python + uses: actions/setup-python@v4 + with: + python-version: '3.10' - - name: Install Python Dependencies - run: pip install pyyaml openpyxl requests + - name: Install Python Dependencies + run: pip install pyyaml openpyxl requests - - name: "[GATE] Run Core 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: "[GATE] Run Core 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: Ensure Temp Directory and Mock Packet - run: | - mkdir -p Temp - # ๋นˆ ํŒจํ‚ท ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜์—ฌ dotnet test/run ์‹œ IO Exception ๋ฐฉ์–ด - 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: Ensure Temp Directory and Mock Packet + 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 Dependencies - run: dotnet restore src/dotnet/QuantEngine.Web/QuantEngine.Web.csproj + - name: Restore 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 \ - -p:Version=1.0.${{ github.run_number }} + - name: Build Release + run: | + dotnet build src/dotnet/QuantEngine.Web/QuantEngine.Web.csproj \ + -c Release \ + --no-restore - - name: Run Unit Tests - run: | - if [ -d tests/unit ]; then - dotnet test tests/unit \ + - 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 \ - || echo "โš ๏ธ Some tests failed (non-blocking for web service)" - fi + -o ./publish - - name: Publish Release Package - run: | - dotnet publish src/dotnet/QuantEngine.Web/QuantEngine.Web.csproj \ - -c Release \ - --no-build \ - -o ./publish-output + - name: Generate Build Info + run: | + COMMIT_HASH=$(git rev-parse --short HEAD) + BUILD_TIME=$(date -d "+9 hours" +'%Y-%m-%d %H:%M:%S KST') + mkdir -p ./publish/wwwroot + printf '{\n "version": "1.0.%s-%s",\n "built": "%s"\n}\n' "${{ github.run_number }}" "$COMMIT_HASH" "$BUILD_TIME" > ./publish/wwwroot/version.json + echo "โœ“ Generated version info: 1.0.${{ github.run_number }}-$COMMIT_HASH @ $BUILD_TIME" - - name: Generate Build Info - run: | - COMMIT_HASH=$(git rev-parse --short HEAD) - BUILD_TIME=$(date -d "+9 hours" +'%Y-%m-%d %H:%M:%S KST') - mkdir -p ./publish-output/wwwroot - printf '{\n "version": "1.0.%s-%s",\n "built": "%s"\n}\n' "${{ github.run_number }}" "$COMMIT_HASH" "$BUILD_TIME" > ./publish-output/wwwroot/version.json - echo "โœ“ Generated version info: 1.0.${{ github.run_number }}-$COMMIT_HASH @ $BUILD_TIME" - - - - name: Setup SSH - run: | - mkdir -p ~/.ssh - chmod 700 ~/.ssh - # SSH_PRIVATE_KEY๊ฐ€ ํ‰๋ฌธ PEM์ด๋“  base64๋“  ์œ ์—ฐํ•˜๊ฒŒ ์ฒ˜๋ฆฌ - if echo "${{ secrets.SSH_PRIVATE_KEY }}" | grep -q "BEGIN"; then - echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_ed25519 - else - echo "${{ secrets.SSH_PRIVATE_KEY }}" | base64 -d > ~/.ssh/id_ed25519 || echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_ed25519 - fi - chmod 600 ~/.ssh/id_ed25519 - ssh-keyscan -H ${{ env.DEPLOY_HOST }} >> ~/.ssh/known_hosts 2>/dev/null || true - - - name: Package Artifact - run: | - tar -czf quant_engine_deploy.tgz -C ./publish-output . - echo "โœ“ Package size: $(du -sh quant_engine_deploy.tgz | cut -f1)" - - - name: Deploy & Verify on Server - run: | - set -e - TIMESTAMP=$(date +%Y%m%d_%H%M%S) - COMMIT=$(git rev-parse --short HEAD) - DEPLOY_HOST="${{ env.DEPLOY_HOST }}" - DEPLOY_USER="${{ env.DEPLOY_USER }}" - - # ํ…”๋ ˆ๊ทธ๋žจ ์„ค์ • ๋ฐ”์ธ๋”ฉ (Secret์— ์—†์„ ๊ฒฝ์šฐ ๊ธฐ๋ณธ๊ฐ’ ๋ฐฑ์—… ์‚ฌ์šฉ) - TELEGRAM_BOT_TOKEN="${{ secrets.TELEGRAM_BOT_TOKEN }}" - [ -z "$TELEGRAM_BOT_TOKEN" ] && TELEGRAM_BOT_TOKEN="${{ env.TELEGRAM_BOT_TOKEN_DEFAULT }}" - TELEGRAM_CHAT_ID="${{ secrets.TELEGRAM_CHAT_ID }}" - [ -z "$TELEGRAM_CHAT_ID" ] && TELEGRAM_CHAT_ID="${{ env.TELEGRAM_CHAT_ID_DEFAULT }}" - - send_telegram() { - local text="$1" - curl -fsS -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \ - -d "chat_id=${TELEGRAM_CHAT_ID}" \ - --data-urlencode "text=${text}" \ - -d "parse_mode=HTML" >/dev/null || true - } - - notify_failure() { - local exit_code=$? - send_telegram "โŒ QuantEngine ๋ฐฐํฌ ์‹คํŒจ - - ์ปค๋ฐ‹: ${COMMIT} - ์‹œ๊ฐ„: ${TIMESTAMP} - ๋‹จ๊ณ„: deploy-to-prod (SSH Execution)" - exit "$exit_code" - } - - trap notify_failure ERR - - echo "=== Deploying QuantEngine $COMMIT ($TIMESTAMP) ===" - - # 1. ์•„ํ‹ฐํŒฉํŠธ ๋ณต์‚ฌ - scp -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i ~/.ssh/id_ed25519 \ - quant_engine_deploy.tgz "$DEPLOY_USER@$DEPLOY_HOST:/tmp/quantengine_${TIMESTAMP}.tgz" - - # 2. ์›๊ฒฉ ๋ฐฐํฌ ๋ช…๋ น์–ด ํ†ตํ•ฉ (SSH 1ํšŒ ์—ฐ๊ฒฐ) - ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i ~/.ssh/id_ed25519 \ - -o ServerAliveInterval=10 \ - "$DEPLOY_USER@$DEPLOY_HOST" bash << REMOTE - set -e - DEPLOY_HOME="/home/kjh2064" - DEPLOY_DIR="\$DEPLOY_HOME/deployments/quantengine_${TIMESTAMP}" - - echo "--- [1/4] ์••์ถ• ํ•ด์ œ ---" - mkdir -p "\$DEPLOY_DIR" - tar -xzf "/tmp/quantengine_${TIMESTAMP}.tgz" -C "\$DEPLOY_DIR" - rm -f "/tmp/quantengine_${TIMESTAMP}.tgz" - - echo "--- [2/4] ์‹ฌ๋ณผ๋ฆญ ๋งํฌ ์ „ํ™˜ ---" - ln -sfn "\$DEPLOY_DIR" "${{ env.DEPLOY_PATH }}" - - echo "--- [3/4] ์„œ๋น„์Šค ์žฌ์‹œ์ž‘ ---" - sudo /usr/bin/systemctl restart ${{ env.SERVICE_NAME }} - - echo "--- [4/4] ํ—ฌ์Šค ์ฒดํฌ ---" - ATTEMPTS=20 - for i in \$(seq 1 \$ATTEMPTS); do - STATUS=\$(curl -sf -o /dev/null -w '%{http_code}' http://127.0.0.1:5000/ 2>/dev/null || echo "000") - if [ "\$STATUS" = "200" ]; then - echo "โœ“ ํ—ฌ์Šค์ฒดํฌ ์„ฑ๊ณต (์‹œ๋„ \$i/\$ATTEMPTS, HTTP 200)" - # ๊ตฌ ๋ฐฐํฌ ํด๋” ์ •๋ฆฌ (์ตœ๊ทผ 5๊ฐœ๋งŒ ๋ณด์กด) - ls -1dt \$DEPLOY_HOME/deployments/quantengine_* 2>/dev/null | tail -n +6 | xargs rm -rf 2>/dev/null || true - exit 0 + - name: Setup SSH + run: | + mkdir -p ~/.ssh + chmod 700 ~/.ssh + if echo "${{ secrets.SSH_PRIVATE_KEY }}" | grep -q "BEGIN"; then + echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_ed25519 + else + echo "${{ secrets.SSH_PRIVATE_KEY }}" | base64 -d > ~/.ssh/id_ed25519 || echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_ed25519 fi - if [ "\$i" -eq "\$ATTEMPTS" ]; then - echo "=== FATAL: ์„œ๋น„์Šค๊ฐ€ ํ—ฌ์Šค์ฒดํฌ ์‘๋‹ต์„ ํ•˜์ง€ ์•Š์Œ ===" >&2 - systemctl is-active ${{ env.SERVICE_NAME }} >&2 || true - journalctl -u ${{ env.SERVICE_NAME }} --no-pager -n 50 >&2 + chmod 600 ~/.ssh/id_ed25519 + ssh-keyscan -H ${{ env.DEPLOY_HOST }} >> ~/.ssh/known_hosts 2>/dev/null || true + + - name: Package Artifact + run: | + tar -czf quantengine.tar.gz -C ./publish . + echo "โœ“ Package size: $(du -sh quantengine.tar.gz | cut -f1)" + + - name: Deploy & Verify on Server + run: | + set -e + TIMESTAMP=$(date +%Y%m%d_%H%M%S) + COMMIT=$(git rev-parse --short HEAD) + DEPLOY_HOST="${{ env.DEPLOY_HOST }}" + DEPLOY_USER="${{ env.DEPLOY_USER }}" + + TELEGRAM_BOT_TOKEN="${{ secrets.TELEGRAM_BOT_TOKEN }}" + [ -z "$TELEGRAM_BOT_TOKEN" ] && TELEGRAM_BOT_TOKEN="${{ env.TELEGRAM_BOT_TOKEN_DEFAULT }}" + TELEGRAM_CHAT_ID="${{ secrets.TELEGRAM_CHAT_ID }}" + [ -z "$TELEGRAM_CHAT_ID" ] && TELEGRAM_CHAT_ID="${{ env.TELEGRAM_CHAT_ID_DEFAULT }}" + + send_telegram() { + local text="$1" + curl -fsS -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \ + -d "chat_id=${TELEGRAM_CHAT_ID}" \ + --data-urlencode "text=${text}" \ + -d "parse_mode=HTML" >/dev/null || true + } + + notify_failure() { + local exit_code=$? + send_telegram "โŒ QuantEngine ๋ฐฐํฌ ์‹คํŒจ + + ์ปค๋ฐ‹: ${COMMIT} + ์‹œ๊ฐ„: ${TIMESTAMP} + ๋‹จ๊ณ„: deploy-to-prod (SSH Execution)" + exit "$exit_code" + } + + trap notify_failure ERR + + echo "=== Deploying QuantEngine $COMMIT ($TIMESTAMP) ===" + + ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i ~/.ssh/id_ed25519 \ + "$DEPLOY_USER@$DEPLOY_HOST" "mkdir -p /home/kjh2064/tmp" + scp -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i ~/.ssh/id_ed25519 \ + quantengine.tar.gz "$DEPLOY_USER@$DEPLOY_HOST:/home/kjh2064/tmp/quantengine.tar.gz" + scp -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i ~/.ssh/id_ed25519 \ + tools/deploy_quantengine.sh "$DEPLOY_USER@$DEPLOY_HOST:/home/kjh2064/tmp/deploy.sh" + + ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i ~/.ssh/id_ed25519 \ + "$DEPLOY_USER@$DEPLOY_HOST" "chmod +x /home/kjh2064/tmp/deploy.sh && CI_DEPLOY=1 /home/kjh2064/tmp/deploy.sh" + + echo "=== Verifying Loopback Health ===" + loopback_html=$(ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i ~/.ssh/id_ed25519 "$DEPLOY_USER@$DEPLOY_HOST" "curl -sf http://127.0.0.1:5000/ || true") + if ! printf '%s' "$loopback_html" | grep -q "Quant Engine"; then + echo "Loopback health check failed for quantengine" >&2 exit 1 fi - echo " ๋Œ€๊ธฐ ์ค‘... (\$i/\$ATTEMPTS, HTTP \$STATUS)" - sleep 3 - done - REMOTE - echo "โœ“ ๋ฐฐํฌ ์™„๋ฃŒ: quantengine_${TIMESTAMP} @ $DEPLOY_HOST" - send_telegram "โœ… QuantEngine ๋ฐฐํฌ ์™„๋ฃŒ - - ์ปค๋ฐ‹: ${COMMIT} - ์‹œ๊ฐ„: ${TIMESTAMP} - ๋Œ€์ƒ: ${DEPLOY_HOST}" + echo "=== Verifying Favicon Assets ===" + favicon_svg_code=$(curl -s -o /dev/null -w "%{http_code}" "http://${DEPLOY_HOST}/favicon.svg") + favicon_png_code=$(curl -s -o /dev/null -w "%{http_code}" "http://${DEPLOY_HOST}/favicon.png") + echo "/favicon.svg -> ${favicon_svg_code}" + echo "/favicon.png -> ${favicon_png_code}" + if [ "$favicon_svg_code" != "200" ] && [ "$favicon_png_code" != "200" ]; then + echo "Favicon assets are not reachable after deploy" >&2 + exit 1 + fi + + echo "=== Verifying Public Routes ===" + root_html=$(curl -sf "http://${DEPLOY_HOST}/" 2>/dev/null || echo "") + ops_html=$(curl -sf "http://${DEPLOY_HOST}/operations" 2>/dev/null || echo "") + + root_code=$(printf '%s' "$root_html" | grep -q "Quant Engine" && echo 200 || echo 500) + ops_code=$(printf '%s' "$ops_html" | grep -q "Operational Report" && echo 200 || echo 500) + + echo "/ -> ${root_code}" + echo "/operations -> ${ops_code}" + + if [ "$root_code" != "200" ]; then + echo "Deployment content check failed for /" >&2 + exit 1 + fi + if [ "$ops_code" != "200" ]; then + echo "Deployment content check failed for /operations" >&2 + exit 1 + fi + + echo "โœ“ ๋ฐฐํฌ ์™„๋ฃŒ: quantengine_${TIMESTAMP} @ $DEPLOY_HOST" + send_telegram "โœ… QuantEngine ๋ฐฐํฌ ์™„๋ฃŒ + + ์ปค๋ฐ‹: ${COMMIT} + ์‹œ๊ฐ„: ${TIMESTAMP} + ๋Œ€์ƒ: ${DEPLOY_HOST}" diff --git a/.gitea/workflows/snapshot_admin_deploy.yml b/.gitea/workflows/snapshot_admin_deploy.yml deleted file mode 100644 index 4ea2a2c..0000000 --- a/.gitea/workflows/snapshot_admin_deploy.yml +++ /dev/null @@ -1,148 +0,0 @@ -name: Snapshot Admin Deployment - -on: - push: - branches: - - main - workflow_dispatch: - -concurrency: - group: snapshot-admin-deploy-main - cancel-in-progress: true - -env: - DEPLOY_HOST: 178.104.200.7 - DEPLOY_USER: kjh2064 - TELEGRAM_BOT_TOKEN_DEFAULT: "8734507814:AAFyacLMai8GB4K-hQ_Nd3t3D01A-h1ZdV0" - TELEGRAM_CHAT_ID_DEFAULT: "-5460205872" - -jobs: - build-and-deploy: - runs-on: ubuntu-latest - timeout-minutes: 15 - steps: - - name: Checkout Code - uses: actions/checkout@v3 - - - name: Setup .NET SDK - uses: actions/setup-dotnet@v3 - with: - dotnet-version: '10.0.x' - - - name: Publish Blazor Web App - run: | - echo "[deploy] publishing .NET 10 Blazor app" - dotnet publish src/dotnet/QuantEngine.Web/QuantEngine.Web.csproj -c Release -o ./publish - - - name: Generate Build Info - run: | - COMMIT_HASH=$(git rev-parse --short HEAD) - BUILD_TIME=$(date -d "+9 hours" +'%Y-%m-%d %H:%M:%S KST') - mkdir -p ./publish/wwwroot - printf '{\n "version": "1.0.%s-%s",\n "built": "%s"\n}\n' "${{ github.run_number }}" "$COMMIT_HASH" "$BUILD_TIME" > ./publish/wwwroot/version.json - echo "โœ“ Generated version info: 1.0.${{ github.run_number }}-$COMMIT_HASH @ $BUILD_TIME" - - - - name: Compress Artifact - run: | - echo "[deploy] compressing publish output" - tar -czf quantengine.tar.gz -C ./publish . - - - name: Setup SSH - run: | - mkdir -p ~/.ssh - chmod 700 ~/.ssh - if echo "${{ secrets.SSH_PRIVATE_KEY }}" | grep -q "BEGIN"; then - echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_ed25519 - else - echo "${{ secrets.SSH_PRIVATE_KEY }}" | base64 -d > ~/.ssh/id_ed25519 || echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_ed25519 - fi - chmod 600 ~/.ssh/id_ed25519 - ssh-keyscan -H ${{ env.DEPLOY_HOST }} >> ~/.ssh/known_hosts 2>/dev/null || true - - - name: Deploy & Verify on Server - run: | - set -e - TIMESTAMP=$(date +%Y%m%d_%H%M%S) - COMMIT=$(git rev-parse --short HEAD) - DEPLOY_HOST="${{ env.DEPLOY_HOST }}" - DEPLOY_USER="${{ env.DEPLOY_USER }}" - - TELEGRAM_BOT_TOKEN="${{ secrets.TELEGRAM_BOT_TOKEN }}" - [ -z "$TELEGRAM_BOT_TOKEN" ] && TELEGRAM_BOT_TOKEN="${{ env.TELEGRAM_BOT_TOKEN_DEFAULT }}" - TELEGRAM_CHAT_ID="${{ secrets.TELEGRAM_CHAT_ID }}" - [ -z "$TELEGRAM_CHAT_ID" ] && TELEGRAM_CHAT_ID="${{ env.TELEGRAM_CHAT_ID_DEFAULT }}" - - send_telegram() { - local text="$1" - curl -fsS -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \ - -d "chat_id=${TELEGRAM_CHAT_ID}" \ - --data-urlencode "text=${text}" \ - -d "parse_mode=HTML" >/dev/null || true - } - - notify_failure() { - local exit_code=$? - send_telegram "โŒ Snapshot Admin ๋ฐฐํฌ ์‹คํŒจ - - ์ปค๋ฐ‹: ${COMMIT} - ์‹œ๊ฐ„: ${TIMESTAMP} - ๋‹จ๊ณ„: snapshot_admin_deploy (Deploy Execution)" - exit "$exit_code" - } - - trap notify_failure ERR - - echo "=== Deploying Snapshot Admin $COMMIT ($TIMESTAMP) ===" - - # 1. ์›๊ฒฉ์ง€ ์ž„์‹œ ํด๋” ์ƒ์„ฑ ๋ฐ ์—…๋กœ๋“œ - ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i ~/.ssh/id_ed25519 "$DEPLOY_USER@$DEPLOY_HOST" "mkdir -p /home/kjh2064/tmp" - scp -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i ~/.ssh/id_ed25519 quantengine.tar.gz "$DEPLOY_USER@$DEPLOY_HOST:/home/kjh2064/tmp/quantengine.tar.gz" - scp -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i ~/.ssh/id_ed25519 tools/deploy_quantengine.sh "$DEPLOY_USER@$DEPLOY_HOST:/home/kjh2064/tmp/deploy.sh" - - # 2. ๋ฐฐํฌ ์Šคํฌ๋ฆฝํŠธ ์‹คํ–‰ - ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i ~/.ssh/id_ed25519 "$DEPLOY_USER@$DEPLOY_HOST" "chmod +x /home/kjh2064/tmp/deploy.sh && CI_DEPLOY=1 /home/kjh2064/tmp/deploy.sh" - - # 3. ๋ฐฐํฌ ์„ฑ๊ณต ๊ฒ€์ฆ - echo "=== Verifying Loopback Health ===" - loopback_html=$(ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i ~/.ssh/id_ed25519 "$DEPLOY_USER@$DEPLOY_HOST" "curl -sf http://127.0.0.1:5000/ || true") - if ! printf '%s' "$loopback_html" | grep -q "Quant Engine"; then - echo "Loopback health check failed for quantengine" >&2 - exit 1 - fi - - echo "=== Verifying Favicon Assets ===" - favicon_svg_code=$(curl -s -o /dev/null -w "%{http_code}" "http://${DEPLOY_HOST}/favicon.svg") - favicon_png_code=$(curl -s -o /dev/null -w "%{http_code}" "http://${DEPLOY_HOST}/favicon.png") - echo "/favicon.svg -> ${favicon_svg_code}" - echo "/favicon.png -> ${favicon_png_code}" - if [ "$favicon_svg_code" != "200" ] && [ "$favicon_png_code" != "200" ]; then - echo "Favicon assets are not reachable after deploy" >&2 - exit 1 - fi - - echo "=== Verifying Public Routes ===" - root_html=$(curl -sf "http://${DEPLOY_HOST}/" 2>/dev/null || echo "") - ops_html=$(curl -sf "http://${DEPLOY_HOST}/operations" 2>/dev/null || echo "") - - root_code=$(printf '%s' "$root_html" | grep -q "Quant Engine" && echo 200 || echo 500) - ops_code=$(printf '%s' "$ops_html" | grep -q "Operational Report" && echo 200 || echo 500) - - echo "/ -> ${root_code}" - echo "/operations -> ${ops_code}" - - if [ "$root_code" != "200" ]; then - echo "Deployment content check failed for /" >&2 - exit 1 - fi - if [ "$ops_code" != "200" ]; then - echo "Deployment content check failed for /operations" >&2 - exit 1 - fi - - echo "โœ“ ๋ฐฐํฌ ์™„๋ฃŒ: quantengine_${TIMESTAMP} @ $DEPLOY_HOST" - send_telegram "โœ… Snapshot Admin ๋ฐฐํฌ ์™„๋ฃŒ - - ์ปค๋ฐ‹: ${COMMIT} - ์‹œ๊ฐ„: ${TIMESTAMP} - ๋Œ€์ƒ: ${DEPLOY_HOST}" diff --git a/AGENTS.md b/AGENTS.md index ba04c47..06b7ef8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -110,7 +110,7 @@ - D+2 ์˜์—…์ผ ๊ธฐ์ค€ ํ˜„๊ธˆ์„ ์ฆ‰์‹œ๋ฐฉ์–ด ์ž์‚ฐ์œผ๋กœ ๊ฐ„์ฃผํ•˜๊ณ , ๋ชฉํ‘œ ์˜ˆ์‚ฐ 5์–ต ์›์„ ๊ธฐ์ค€์œผ๋กœ ํฌ์ง€์…˜ ์‚ฌ์ด์ง• ๋ฐ ๋ฆฌ์Šคํฌ ๋ฒ„ํ‚ท์„ ์ œ์–ดํ•œ๋‹ค. - ๋งค์ฃผ ์ฃผ๋ง ๋ฆฌ๋ฐธ๋Ÿฐ์‹ฑ(rebalance_required=true) ๋ฐ ๋งค์›” 1์ผ/11์ผ/21์ผ ์ค‘๊ฐ„์ ๊ฒ€(mid_check_required=true) ์šด์˜ cadence๋ฅผ ์ค€์ˆ˜ํ•œ๋‹ค. - ์ปค๋ฐ‹, ํ‘ธ์‰ฌ, PR ์ž‘์—… ์‹œ ๋ฐ˜๋“œ์‹œ ๋กœ์ปฌ์˜ .gs ํŒŒ์ผ์„ Google Apps Script ์›๊ฒฉ ํ”„๋กœ์ ํŠธ์— ์—…๋กœ๋“œ(python tools/deploy_gas.py ์‹คํ–‰)ํ•˜๊ณ , ์‚ฌ์šฉ์ž์—๊ฒŒ ์Šคํ”„๋ ˆ๋“œ์‹œํŠธ ์ƒ์˜ ์Šคํฌ๋ฆฝํŠธ ์‹คํ–‰(์˜ˆ: runDataFeed)์„ ํ†ตํ•œ ๊ฒ€์ฆ์„ ์œ ๋„ ๋ฐ ๊ฐ€์ด๋“œํ•ด์•ผ ํ•œ๋‹ค. -- QuantEngine ๋ฐฐํฌ๋Š” CI ์ „์šฉ์ด๋‹ค. ๋กœ์ปฌ์—์„œ ์„œ๋ฒ„๋กœ ์‚ฐ์ถœ๋ฌผ์„ ์ง์ ‘ ์—…๋กœ๋“œํ•˜๊ฑฐ๋‚˜ `scp`/`rsync`๋กœ ์ˆ˜๋™ ๋ฐ˜์˜ํ•˜์ง€ ์•Š๋Š”๋‹ค. ์‹ค๋ฐฐํฌ๋Š” `.gitea/workflows/snapshot_admin_deploy.yml`๋งŒ ์‚ฌ์šฉํ•˜๋ฉฐ, ๋กœ์ปฌ ์Šคํฌ๋ฆฝํŠธ๋Š” CI ํ™˜๊ฒฝ์—์„œ๋งŒ ์‹คํ–‰ ๊ฐ€๋Šฅํ•ด์•ผ ํ•œ๋‹ค. +- QuantEngine ๋ฐฐํฌ๋Š” CI ์ „์šฉ์ด๋‹ค. ๋กœ์ปฌ์—์„œ ์„œ๋ฒ„๋กœ ์‚ฐ์ถœ๋ฌผ์„ ์ง์ ‘ ์—…๋กœ๋“œํ•˜๊ฑฐ๋‚˜ `scp`/`rsync`๋กœ ์ˆ˜๋™ ๋ฐ˜์˜ํ•˜์ง€ ์•Š๋Š”๋‹ค. ์‹ค๋ฐฐํฌ๋Š” `.gitea/workflows/deploy-prod.yml`๋งŒ ์‚ฌ์šฉํ•˜๋ฉฐ, ๋กœ์ปฌ ์Šคํฌ๋ฆฝํŠธ๋Š” CI ํ™˜๊ฒฝ์—์„œ๋งŒ ์‹คํ–‰ ๊ฐ€๋Šฅํ•ด์•ผ ํ•œ๋‹ค. - ์›๊ฒฉ ์„œ๋ฒ„ ํ™•์ธ์ด ํ•„์š”ํ•˜๋ฉด `ssh kjh2064@178.104.200.7` ์ ‘์†์„ ๋จผ์ € ์‹œ๋„ํ•˜๊ณ , ์‚ฌ์šฉ์ž์—๊ฒŒ ๋งค๋ฒˆ ์ ‘์† ํ™•์ธ์„ ์š”๊ตฌํ•˜์ง€ ๋ง๊ณ  ์ง์ ‘ ์ƒํƒœ/๋กœ๊ทธ/ํ—ฌ์Šค์ฒดํฌ๋ฅผ ์ˆ˜์ง‘ํ•œ ๋’ค ๊ฒฐ๊ณผ๋งŒ ๋ณด๊ณ ํ•œ๋‹ค. ## 4. ๋ณด๊ณ  ๊ทœ์น™ diff --git a/README.md b/README.md index f1a4691..d06443a 100644 --- a/README.md +++ b/README.md @@ -144,7 +144,7 @@ npm run prepare-upload-zip ## CI / ๋ฐฐํฌ ๋ถ„๋ฆฌ - `.gitea/workflows/ci.yml`์€ ๊ฒ€์ฆ ์ „์šฉ์ด๋‹ค. -- `.gitea/workflows/snapshot_admin_deploy.yml`์€ ์‹ค๋ฐฐํฌ ์ „์šฉ์ด๋‹ค. +- `.gitea/workflows/deploy-prod.yml`์€ ์‹ค๋ฐฐํฌ ์ „์šฉ์ด๋‹ค. - ๊ณต๊ฐœ URL `http://178.104.200.7/quant/` ๊ฐฑ์‹ ์€ deploy workflow ์„ฑ๊ณต ์—ฌ๋ถ€๋กœ ํŒ๋‹จํ•œ๋‹ค. ## ์šด์˜ ๋ฆฌํฌํŠธ ๊ณ„์•ฝ diff --git a/docs/CLOUD_SERVER_SETUP.md b/docs/CLOUD_SERVER_SETUP.md index 35058dc..5e2b629 100644 --- a/docs/CLOUD_SERVER_SETUP.md +++ b/docs/CLOUD_SERVER_SETUP.md @@ -206,9 +206,9 @@ services: ### 6.4. CI / ๋ฐฐํฌ ๋ถ„๋ฆฌ - `.gitea/workflows/ci.yml`: ๊ฒ€์ฆ ์ „์šฉ. ์ŠคํŽ™/๊ณต์‹/๋ฆฌํฌํŠธ/์•„ํ‹ฐํŒฉํŠธ ์ƒ์„ฑ๊นŒ์ง€๋งŒ ์ˆ˜ํ–‰ํ•œ๋‹ค. -- `.gitea/workflows/snapshot_admin_deploy.yml`: ์‹ค๋ฐฐํฌ ์ „์šฉ. `dotnet publish` ํ›„ `tools/deploy_quantengine.sh`๋ฅผ ์ด์šฉํ•ด `/home/kjh2064/quantengine_active`๋กœ ๋ฐ˜์˜ํ•œ๋‹ค. +- `.gitea/workflows/deploy-prod.yml`: ์‹ค๋ฐฐํฌ ์ „์šฉ. `dotnet publish` ํ›„ `tools/deploy_quantengine.sh`๋ฅผ ์ด์šฉํ•ด `/home/kjh2064/quantengine_active`๋กœ ๋ฐ˜์˜ํ•œ๋‹ค. - ์ˆ˜๋™ ๋ฐฐํฌ ๊ธˆ์ง€: ๋กœ์ปฌ์—์„œ `scp`/`rsync`๋กœ `quantengine_active`๋ฅผ ๊ฐฑ์‹ ํ•˜์ง€ ์•Š๋Š”๋‹ค. ๋ฐฐํฌ๋Š” CI๊ฐ€ ์›๊ฒฉ์—์„œ๋งŒ ์ˆ˜ํ–‰ํ•˜๊ณ , ๋กœ์ปฌ ์Šคํฌ๋ฆฝํŠธ๋Š” `CI_DEPLOY=1` ์—†์ด ์‹คํ–‰๋˜๋ฉด ์‹คํŒจํ•ด์•ผ ํ•œ๋‹ค. -- ๊ณต๊ฐœ URL `/quant/` ๊ฐฑ์‹ ์€ `snapshot_admin_deploy.yml`์˜ ์„ฑ๊ณต ์—ฌ๋ถ€๋ฅผ ๊ธฐ์ค€์œผ๋กœ ํŒ๋‹จํ•œ๋‹ค. +- ๊ณต๊ฐœ URL ๊ฐฑ์‹ ์€ `deploy-prod.yml`์˜ ์„ฑ๊ณต ์—ฌ๋ถ€๋ฅผ ๊ธฐ์ค€์œผ๋กœ ํŒ๋‹จํ•œ๋‹ค. ### 6.2. ๋Ÿฌ๋„ˆ ์„ค์ • @@ -404,7 +404,7 @@ docker ps -a ```bash # CI์—์„œ๋งŒ ๋ฐฐํฌ # ๋กœ์ปฌ์—์„œ scp/rsync๋กœ quantengine_active๋ฅผ ๊ฐฑ์‹ ํ•˜์ง€ ์•Š๋Š”๋‹ค. -# ๋ฐฐํฌ๋Š” .gitea/workflows/snapshot_admin_deploy.yml ์‹คํ–‰ ๊ฒฐ๊ณผ๋กœ๋งŒ ๋ฐ˜์˜ํ•œ๋‹ค. +# ๋ฐฐํฌ๋Š” .gitea/workflows/deploy-prod.yml ์‹คํ–‰ ๊ฒฐ๊ณผ๋กœ๋งŒ ๋ฐ˜์˜ํ•œ๋‹ค. ``` ### Gitea Act Runner ๋“ฑ๋ก diff --git a/docs/ROADMAP_WBS.md b/docs/ROADMAP_WBS.md index 2ecf19e..d73b47f 100644 --- a/docs/ROADMAP_WBS.md +++ b/docs/ROADMAP_WBS.md @@ -925,7 +925,7 @@ python tools/validate_specs.py โ†’ PASS |------|------| | **์ž‘์—…** | `src/quant_engine/snapshot_admin_server_v1.py`(Python ์–ด๋“œ๋ฏผ ์›น UI)๋ฅผ Gitea CI/CD ๋ฐฐํฌ ์Šคํ…์„ ํ†ตํ•ด Synology NAS์—์„œ ์ƒ์‹œ ์„œ๋น„์Šค๋กœ ์šด์˜ํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ๊ฒ€ํ†  | | **ํ˜„์žฌ ์ƒํƒœ** | **๊ธฐ์ˆ ์ ์œผ๋กœ๋Š” ๊ฐ€๋Šฅ**. ๊ธฐ๋ณธ ๋ฃจํ”„๋ฐฑ ๋ณดํ˜ธ + Basic Auth ๊ฒŒ์ดํŠธ๋ฅผ ์ถ”๊ฐ€ํ–ˆ๊ณ , Synology ์™ธ๋ถ€ ๋…ธ์ถœ์€ ๋ฆฌ๋ฒ„์Šค ํ”„๋ก์‹œ ๊ธฐ๋ฐ˜ POC๋กœ ๊ฐ€์ด๋“œํ•จ. ์‹ค๋ฐฐํฌ ๊ฒ€์ฆ์€ ์•„์ง ํ•„์š” | -| **์šด์˜ ๋ถ„๋ฆฌ** | `snapshot_admin.yml`์€ `push`์šฉ smoke ๊ฒ€์ฆ๊ณผ `workflow_dispatch`์šฉ full ๊ฒ€์ฆ์œผ๋กœ ๋ถ„๋ฆฌํ•˜๊ณ , ๋ฐฐํฌ๋Š” ๋ณ„๋„ `snapshot_admin_deploy.yml` `workflow_dispatch`๋กœ ๋–ผ์–ด๋ƒˆ๋‹ค. `push`์—์„œ๋Š” `Validate Snapshot Admin Workflow`๊นŒ์ง€๋งŒ, full ๊ฒ€์ฆ์—์„œ๋Š” `Validate Snapshot Admin Web UI`๊นŒ์ง€ ์ˆ˜ํ–‰ํ•œ๋‹ค. | +| **์šด์˜ ๋ถ„๋ฆฌ** | `snapshot_admin.yml`์€ `push`์šฉ smoke ๊ฒ€์ฆ๊ณผ `workflow_dispatch`์šฉ full ๊ฒ€์ฆ์œผ๋กœ ๋ถ„๋ฆฌํ•˜๊ณ , ๋ฐฐํฌ๋Š” ๋ณ„๋„ `deploy-prod.yml` `workflow_dispatch`๋กœ ๋–ผ์–ด๋ƒˆ๋‹ค. `push`์—์„œ๋Š” `Validate Snapshot Admin Workflow`๊นŒ์ง€๋งŒ, full ๊ฒ€์ฆ์—์„œ๋Š” `Validate Snapshot Admin Web UI`๊นŒ์ง€ ์ˆ˜ํ–‰ํ•œ๋‹ค. | | **runner ์ฃผ์˜** | Gitea runner๋ฅผ Docker mode๋กœ ๋‘๋ฉด job ์ข…๋ฃŒ ์‹œ `Cleaning up container` ๋กœ๊ทธ๊ฐ€ ๋‚จ๋Š”๋‹ค. host label๋กœ ์žฌ๋“ฑ๋กํ•˜๋ฉด job container ์ •๋ฆฌ ๋กœ๊ทธ๋ฅผ ํ”ผํ•  ์ˆ˜ ์žˆ๋‹ค. | | **KIS ๋ถ„๋ฆฌ** | `kis_data_collection.yml`์€ `workflow_dispatch`์šฉ mock/config smoke์™€ `schedule`์šฉ live collection์œผ๋กœ ๋ถ„๋ฆฌํ–ˆ๋‹ค. ์ˆ˜๋™ ๋””์ŠคํŒจ์น˜๋Š” ์‹ค์ œ ์ˆ˜์ง‘์„ ๋Œ๋ฆฌ์ง€ ์•Š๊ณ , ์‹ค์ˆ˜์ง‘์€ ์Šค์ผ€์ค„ ์ „์šฉ์ด๋‹ค. | | **๋‹ด๋‹น ํŒŒ์ผ** | `.gitea/workflows/ci.yml`, `tools/run_snapshot_admin_server_v1.py`, `src/quant_engine/snapshot_admin_server_v1.py`, `docs/SYNOLOGY_SNAPSHOT_ADMIN_POC.md`, `docs/WBS_7_9_EVIDENCE_PACKET_FINAL.md` | @@ -1651,7 +1651,7 @@ WBS-10.1 (๊ธฐ๋ฐ˜ ๊ฒฐํ•จ ์ˆ˜์ •) | 10.10.2 | Dashboard ์ƒํƒœ ํŽ˜์ด์ง€ โ€” ๋ฐ์ดํ„ฐ ๋น„์˜์กดํ˜• ์š”์•ฝ์œผ๋กœ ๋‹จ์ˆœํ™” | DB ์‹คํŒจ ์‹œ์—๋„ 200 ์‘๋‹ต (์™„๋ฃŒ) | | 10.10.3 | Counter.razor / Weather.razor ๊ธฐ๋ณธ ํŽ˜์ด์ง€ ์‚ญ์ œ, NavMenu ์ •๋น„ | ๋ถˆํ•„์š” ํŽ˜์ด์ง€ 0๊ฑด, NavMenu์— Dashboard/Operations๋งŒ ํ‘œ์‹œ (์™„๋ฃŒ) | | 10.10.4 | ๋‹คํฌ ๋ชจ๋“œ + ๋ฐ˜์‘ํ˜• ๋ ˆ์ด์•„์›ƒ ์ ์šฉ | ๋ธŒ๋ผ์šฐ์ € ๋ Œ๋”๋ง ์ •์ƒ ํ™•์ธ (์™„๋ฃŒ) | -| 10.10.5 | ๋ฐฐํฌ ๋™๊ธฐํ™” | `snapshot_admin_deploy.yml`๊ฐ€ `/quant/`์™€ `/quant/operations` ๊ณต๊ฐœ ๋ผ์šฐํŠธ๋ฅผ ๋ฐฐํฌ ํ›„ ๊ฒ€์ฆํ•˜๋„๋ก ๊ตฌ์„ฑ๋จ (์™„๋ฃŒ) | +| 10.10.5 | ๋ฐฐํฌ ๋™๊ธฐํ™” | `deploy-prod.yml`๊ฐ€ ๊ณต๊ฐœ ๋ผ์šฐํŠธ๋ฅผ ๋ฐฐํฌ ํ›„ ๊ฒ€์ฆํ•˜๋„๋ก ๊ตฌ์„ฑ๋จ (์™„๋ฃŒ) | **์„ฑ๊ณต ํ•˜๋„ค์Šค (๋ฐ์ดํ„ฐ ๊ธฐ์ค€)**: ```