fix: 퀀트봇 페이지 수익률 소수점 반환으로 수정 #281
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Build & Deploy (backend + frontend) | |
| on: | |
| workflow_dispatch: {} | |
| push: | |
| branches: ["main"] | |
| paths: | |
| - "backend/**" | |
| - "frontend/**" # 프론트 변경에도 트리거 | |
| - "docker-compose.yml" | |
| - ".github/workflows/deploy.yml" | |
| concurrency: | |
| group: deploy-main | |
| cancel-in-progress: true | |
| env: | |
| REGISTRY: ghcr.io | |
| IMAGE_BACK: ghcr.io/sisc-it/sisc-web-back | |
| IMAGE_FRONT: ghcr.io/sisc-it/sisc-web-front # 프론트 이미지 추가 | |
| jobs: | |
| # 1. 변경 감지 (변동 없음) | |
| changes: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| back: ${{ steps.filter.outputs.back }} | |
| front: ${{ steps.filter.outputs.front }} | |
| steps: | |
| - name: Checkout (full history) | |
| uses: actions/checkout@v4 | |
| #with: # Git 히스토리를 처음부터 끝까지 가져오기. | |
| # fetch-depth: 0 # 개발 배포는 최신 코드만 있으면 됨 (기본값 1) | |
| - name: Detect path changes | |
| id: filter | |
| uses: dorny/paths-filter@v3 | |
| with: | |
| filters: | | |
| back: | |
| - 'backend/**' | |
| - 'docker-compose.yml' # ⬅ 추가 | |
| - '.github/workflows/deploy.yml' # ⬅ 추가 | |
| front: | |
| - 'frontend/**' | |
| - 'docker-compose.yml' # ⬅ 추가 | |
| - '.github/workflows/deploy.yml' # ⬅ 추가 | |
| # 2. 백엔드 빌드 | |
| build-back: | |
| needs: [changes] # deploy는 job 들이 병렬 처리가 되므로, 리스트 항목이 모두 끝나야 시작된다는 조건 추가 | |
| #if: ${{ needs.changes.outputs.back == 'true' || github.event_name == 'workflow_dispatch' }} | |
| environment: development | |
| runs-on: ubuntu-latest | |
| permissions: { contents: read, packages: write } | |
| #defaults: | |
| # run: | |
| # working-directory: backend # context: ./backend 로 지정했기 때문에 필요X | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: docker/login-action@v3 | |
| with: | |
| registry: ${{ env.REGISTRY }} | |
| username: ${{ github.actor }} # 실행 중인 유저 이름 자동 할당 | |
| password: ${{ secrets.GITHUB_TOKEN }} # 별도 설정 없이 바로 사용 가능 | |
| - uses: docker/setup-buildx-action@v3 | |
| # 이미지 태그를 예쁘게 만들지만, 개발서버는 latest만 있으면 됨. (예: sha-a1b2c3d, v1.0.0, pr-123) | |
| #- name: Meta (tags/labels) - backend | |
| # id: meta-back | |
| # uses: docker/metadata-action@v5 | |
| # with: | |
| # images: ${{ env.IMAGE_BACK }} | |
| # tags: | | |
| # type=raw,value=latest | |
| # type=sha,prefix=sha-,format=short | |
| - name: Build & Push (backend) | |
| uses: docker/build-push-action@v6 | |
| with: | |
| context: ./backend | |
| push: true | |
| tags: ${{ env.IMAGE_BACK }}:latest | |
| #tags: ${{ steps.meta-back.outputs.tags }} | |
| #labels: ${{ steps.meta-back.outputs.labels }} | |
| #cache-from: type=gha | |
| #cache-to: type=gha,mode=max | |
| build-front: | |
| needs: [changes] # deploy는 job 들이 병렬 처리가 되므로, 리스트 항목이 모두 끝나야 시작된다는 조건 추가 | |
| #if: ${{ needs.changes.outputs.front == 'true' || github.event_name == 'workflow_dispatch' }} | |
| runs-on: ubuntu-latest | |
| environment: development # development 이라는 environment에 있는 secrets 들을 쓰기 위함 (secrets.FRONTEND_URL) | |
| permissions: { contents: read, packages: write } | |
| #defaults: # context: ./frontend | |
| # run: | |
| # working-directory: frontend | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: docker/login-action@v3 | |
| with: | |
| registry: ${{ env.REGISTRY }} | |
| username: ${{ github.actor }} # 실행 중인 유저 이름 자동 할당 | |
| password: ${{ secrets.GITHUB_TOKEN }} # 별도 설정 없이 바로 사용 가능 | |
| #- name: Meta (tags/labels) - frontend | |
| # id: meta-front | |
| # uses: docker/metadata-action@v5 | |
| # with: | |
| # images: ${{ env.IMAGE_FRONT }} | |
| # tags: | | |
| # type=raw,value=latest | |
| # type=sha,prefix=sha-,format=short | |
| - uses: docker/setup-buildx-action@v3 | |
| - name: Build & Push (frontend) | |
| uses: docker/build-push-action@v6 | |
| with: | |
| context: ./frontend # ⬅ 프론트 도커 컨텍스트 위치 | |
| push: true | |
| platforms: linux/amd64 | |
| tags: ${{ env.IMAGE_FRONT }}:latest | |
| #tags: ${{ steps.meta-front.outputs.tags }} | |
| #labels: ${{ steps.meta-front.outputs.labels }} | |
| build-args: | | |
| VITE_API_URL=${{ secrets.SPRING_API_URL }} | |
| #cache-from: type=gha | |
| #cache-to: type=gha,mode=max | |
| deploy: | |
| #if: ${{ always() && (needs.build-back.result == 'success' || needs.build-front.result == 'success') }} | |
| needs: [changes, build-back, build-front] # deploy는 job 들이 병렬 처리가 되므로, 리스트 항목이 모두 끝나야 시작된다는 조건 추가 | |
| # - manual 실행(workflow_dispatch)이면 무조건 실행 | |
| # - push일 때는 둘 중 하나라도 성공한 경우에만 실행 (둘 다 failure가 아닌 이상 OK) | |
| runs-on: ubuntu-latest | |
| environment: development # development 환경의 repository secrets 사용 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Make .env file | |
| run: | | |
| # 1. 기본 설정, 첫 줄은 > (덮어쓰기, >>:이어쓰기) | |
| echo "SPRING_PROFILES_ACTIVE=prod" > .env | |
| # 2. DB (GitHub Secrets) | |
| echo "DB_URL=${{ secrets.DB_URL }}" >> .env | |
| echo "DB_USERNAME=${{ secrets.DB_USERNAME }}" >> .env | |
| echo "DB_PASSWORD=${{ secrets.DB_PASSWORD }}" >> .env | |
| # 3. 기타 Secrets | |
| echo "SPRING_API_URL=${{ secrets.SPRING_API_URL }}" >> .env | |
| echo "FRONTEND_URL=${{ secrets.FRONTEND_URL }}" >> .env | |
| echo "JWT_SECRET=${{ secrets.JWT_SECRET }}" >> .env | |
| echo "MAIL_USERNAME=${{ secrets.MAIL_USERNAME }}" >> .env | |
| echo "MAIL_PASSWORD=${{ secrets.MAIL_PASSWORD }}" >> .env | |
| echo "SBA_ADMIN_NAME=${{ secrets.SBA_ADMIN_NAME }}" >> .env | |
| echo "SBA_ADMIN_PASSWORD=${{ secrets.SBA_ADMIN_PASSWORD }}" >> .env | |
| # 필요하다면 풀 사이즈 변수도 추가 | |
| # echo "DB_POOL_MAX=30" >> .env | |
| # echo "STOCK_POOL_MAX=10" >> .env | |
| - name: 파일 전송 (docker-compose, .env to EC2) | |
| uses: appleboy/scp-action@master | |
| with: | |
| host: ${{ secrets.SSH_HOST }} | |
| username: ${{ secrets.SSH_USER }} | |
| key: ${{ secrets.SSH_PRIVATE_KEY }} | |
| port: ${{ secrets.SSH_PORT }} | |
| source: "docker-compose.yml, .env" | |
| target: "/home/${{ secrets.SSH_USER }}/app/sisc-web/" | |
| - name: SSH deploy | |
| uses: appleboy/ssh-action@v1.2.0 | |
| with: | |
| host: ${{ secrets.SSH_HOST }} | |
| username: ${{ secrets.SSH_USER }} | |
| key: ${{ secrets.SSH_PRIVATE_KEY }} | |
| port: ${{ secrets.SSH_PORT }} | |
| script_stop: true | |
| script: | | |
| set -euo pipefail | |
| # 필수 폴더 생성 및 권한 설정 (최초 1회 혹은 누락 대비) | |
| # sudo mkdir -p /mnt/storage/pg_ai_data /mnt/storage/logs /mnt/storage/uploads ./data/postgres | |
| # sudo chown -R 70:70 /mnt/storage/pg_ai_data ./data/postgres # DB 전용 | |
| # sudo chmod -R 777 /mnt/storage/logs /mnt/storage/uploads # 앱 공용 | |
| cd ~/app/sisc-web | |
| # GHCR 로그인 (백/프론트 둘 다 같은 레지스트리 사용) | |
| echo "${{ secrets.GHCR_READ_TOKEN }}" | docker login ghcr.io -u ${{ secrets.GHCR_READ_USER }} --password-stdin | |
| # 실행 | |
| docker compose pull api web redis npm # 최신 이미지 받기 (backend, frontend만) | |
| docker compose up -d --remove-orphans api web redis npm db # 컨테이너 재기동 (backend, frontend만) | |
| docker image prune -a -f | |
| # 백엔드 헬스체크 (서비스 이름이 api일 때) | |
| if docker ps --format '{{.Names}}' | grep -q "^api$"; then | |
| echo "Waiting for api to be healthy..." | |
| for i in {1..30}; do | |
| status=$(docker inspect --format='{{json .State.Health.Status}}' api 2>/dev/null || echo '"none"') | |
| if echo "$status" | grep -q healthy; then | |
| echo "=== 서버 정상 작동 확인 ==="; break | |
| fi | |
| sleep 2 | |
| done | |
| fi | |
| # 도커 용량 최적화 | |
| - name: Cleanup Docker | |
| run: | | |
| # 빌드 과정에서 생긴 모든 캐시 삭제 (-a 옵션이 핵심) | |
| docker builder prune -a -f | |
| # 태그가 없어진 이미지 삭제 | |
| docker image prune -a -f |