diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 4789a57..a2523bb 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -1,94 +1,190 @@ -name: Docker Build and Push +name: Docker Build and Deploy on: push: - branches: [ main ] # 只在 main 分支推送时触发 - workflow_dispatch: # 支持手动触发 + branches: [ main ] + paths-ignore: + - '**.md' + - 'docs/**' + workflow_dispatch: + inputs: + deploy: + description: '是否部署' + type: boolean + default: true env: DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }} - CACHE_FROM_BACKEND: type=registry,ref=${{ secrets.DOCKERHUB_USERNAME }}/stock-scanner-backend:buildcache - CACHE_TO_BACKEND: type=registry,ref=${{ secrets.DOCKERHUB_USERNAME }}/stock-scanner-backend:buildcache,mode=max - CACHE_FROM_FRONTEND: type=registry,ref=${{ secrets.DOCKERHUB_USERNAME }}/stock-scanner-frontend:buildcache - CACHE_TO_FRONTEND: type=registry,ref=${{ secrets.DOCKERHUB_USERNAME }}/stock-scanner-frontend:buildcache,mode=max + TIME: ${{ github.run_number }}-${{ github.run_attempt }} jobs: - build: + prepare: runs-on: ubuntu-latest + outputs: + version: ${{ steps.version.outputs.version }} + steps: + - name: Generate version + id: version + run: | + echo "version=$(date +'%Y%m%d%H%M')" >> $GITHUB_OUTPUT + + build: + needs: prepare + runs-on: ubuntu-latest + strategy: + matrix: + service: [backend, frontend] + include: + - service: backend + context: . + - service: frontend + context: ./frontend + # 允许其中一个任务失败时,其他任务仍继续执行 + fail-fast: false steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up QEMU - uses: docker/setup-qemu-action@v2 - - - name: Get current time - id: time - run: echo "TIME=$(date +'%Y%m%d%H%M')" >> $GITHUB_ENV + uses: docker/setup-qemu-action@v3 - name: Login to Docker Hub - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 + uses: docker/setup-buildx-action@v3 with: platforms: linux/amd64,linux/arm64 - # 构建后端镜像 - - name: Build and push backend - uses: docker/build-push-action@v4 + - name: Docker meta + id: meta + uses: docker/metadata-action@v5 with: - context: . - platforms: linux/amd64,linux/arm64 - push: true + images: ${{ secrets.DOCKERHUB_USERNAME }}/stock-scanner-${{ matrix.service }} tags: | - ${{ secrets.DOCKERHUB_USERNAME }}/stock-scanner-backend:latest - ${{ secrets.DOCKERHUB_USERNAME }}/stock-scanner-backend:${{ env.TIME }} - cache-from: ${{ env.CACHE_FROM_BACKEND }} - cache-to: ${{ env.CACHE_TO_BACKEND }} + type=raw,value=latest + type=raw,value=${{ needs.prepare.outputs.version }} - # 构建前端镜像 - - name: Build and push frontend - uses: docker/build-push-action@v4 + - name: Build and push + uses: docker/build-push-action@v5 with: - context: ./frontend + context: ${{ matrix.context }} platforms: linux/amd64,linux/arm64 push: true - tags: | - ${{ secrets.DOCKERHUB_USERNAME }}/stock-scanner-frontend:latest - ${{ secrets.DOCKERHUB_USERNAME }}/stock-scanner-frontend:${{ env.TIME }} - cache-from: ${{ env.CACHE_FROM_FRONTEND }} - cache-to: ${{ env.CACHE_TO_FRONTEND }} + tags: ${{ steps.meta.outputs.tags }} + cache-from: type=gha,scope=${{ matrix.service }} + cache-to: type=gha,scope=${{ matrix.service }},mode=max deploy: - needs: build + needs: [prepare, build] runs-on: ubuntu-latest - if: github.ref == 'refs/heads/main' && github.event_name == 'push' + if: | + success() && + (github.ref == 'refs/heads/main' && github.event_name == 'push') || + (github.event_name == 'workflow_dispatch' && inputs.deploy) steps: + - name: Checkout code for prod compose file + uses: actions/checkout@v4 + with: + sparse-checkout: | + docker-compose.prod.yml + + - name: Create .env file for deployment + run: | + cat > .env << EOL + DOCKERHUB_USERNAME=${{ secrets.DOCKERHUB_USERNAME }} + TAG=${{ needs.prepare.outputs.version }} + API_KEY=${{ secrets.API_KEY }} + API_URL=${{ secrets.API_URL }} + API_MODEL=${{ secrets.API_MODEL }} + API_TIMEOUT=${{ secrets.API_TIMEOUT }} + LOGIN_PASSWORD=${{ secrets.LOGIN_PASSWORD }} + ANNOUNCEMENT_TEXT=${{ secrets.ANNOUNCEMENT_TEXT }} + EOL + - name: Deploy to server uses: appleboy/ssh-action@master with: host: ${{ secrets.SERVER_HOST }} username: ${{ secrets.SERVER_USERNAME }} key: ${{ secrets.SSH_PRIVATE_KEY }} + script_stop: true # 遇到错误时停止执行 + envs: DEPLOY_PATH + script: | + # 创建备份目录(如果不存在) + mkdir -p ${DEPLOY_PATH}/backups + + # 如果存在旧容器,备份当前的配置和数据 + if [ -f ${DEPLOY_PATH}/docker-compose.prod.yml ]; then + cp ${DEPLOY_PATH}/docker-compose.prod.yml ${DEPLOY_PATH}/backups/docker-compose.prod.$(date +%Y%m%d%H%M%S).yml + if [ -f ${DEPLOY_PATH}/.env ]; then + cp ${DEPLOY_PATH}/.env ${DEPLOY_PATH}/backups/.env.$(date +%Y%m%d%H%M%S) + fi + fi + + - name: Copy files to server + uses: appleboy/scp-action@master + with: + host: ${{ secrets.SERVER_HOST }} + username: ${{ secrets.SERVER_USERNAME }} + key: ${{ secrets.SSH_PRIVATE_KEY }} + source: "docker-compose.prod.yml,.env" + target: ${{ secrets.DEPLOY_PATH }} + overwrite: true + + - name: Start services + uses: appleboy/ssh-action@master + with: + host: ${{ secrets.SERVER_HOST }} + username: ${{ secrets.SERVER_USERNAME }} + key: ${{ secrets.SSH_PRIVATE_KEY }} + script_stop: true script: | cd ${{ secrets.DEPLOY_PATH }} - # 设置环境变量 - export DOCKERHUB_USERNAME=${{ secrets.DOCKERHUB_USERNAME }} - export TAG=${{ env.TIME }} - - # 拉取最新代码 - git pull - - # 拉取最新镜像并重启服务 + # 拉取最新镜像并启动服务 docker compose -f docker-compose.prod.yml pull docker compose -f docker-compose.prod.yml up -d + # 等待服务启动完成 + echo "等待服务启动..." + sleep 10 + + # 验证服务是否正常运行 + if ! curl -s http://localhost:80 > /dev/null; then + echo "前端服务未正常运行!" + exit 1 + fi + + if ! curl -s http://localhost:8888/config > /dev/null; then + echo "后端服务未正常运行!" + exit 1 + fi + # 清理未使用的镜像和容器 - docker system prune -f \ No newline at end of file + docker system prune -af --volumes + + echo "部署完成并验证成功!" + + notify: + needs: [prepare, build, deploy] + runs-on: ubuntu-latest + if: always() + steps: + - name: Notify deployment status + uses: rtCamp/action-slack-notify@v2 + env: + SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} + SLACK_CHANNEL: deployments + SLACK_COLOR: ${{ needs.deploy.result == 'success' && 'good' || needs.deploy.result == 'skipped' && 'warning' || 'danger' }} + SLACK_TITLE: Stock Scanner Deployment Status + SLACK_MESSAGE: | + Build: ${{ needs.build.result == 'success' && '✅' || '❌' }} + Deploy: ${{ needs.deploy.result == 'success' && '✅' || needs.deploy.result == 'skipped' && '⏭️' || '❌' }} + Version: ${{ needs.prepare.outputs.version }} + SLACK_FOOTER: GitHub Actions \ No newline at end of file diff --git a/README.md b/README.md index d510ace..88e87fd 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,8 @@ 3. 完善Dockerfile、GitHub Actions 支持docker一键部署使用。 4. 支持x86_64 和 ARM64架构镜像 5. 支持流式输出,支持前端传入Key(仅作为本地用户使用,日志等内容不会输出) 感谢@Cassianvale +6. 重构为Vue3+Vite+TS+Naive UI,支持响应式布局 +7. 支持GitHub Actions 一键部署 ## docker一键部署 ``` @@ -46,6 +48,18 @@ API_URL 处理逻辑说明: ``` 默认8888端口,部署完成后访问 http://127.0.0.1:8888 即可使用。 +## Github Actions 部署 + +| 环境变量 | 说明 | +| --- | --- | +| DOCKERHUB_USERNAME | Docker Hub用户名 | +| DOCKERHUB_TOKEN | Docker Hub访问令牌 | +| SERVER_HOST | 部署服务器地址 | +| SERVER_USERNAME | 服务器用户名 | +| SSH_PRIVATE_KEY | SSH私钥 | +| DEPLOY_PATH | 部署路径 | +| SLACK_WEBHOOK | Slack通知Webhook(可选) | + ## 注意事项 (Notes) - 股票分析仅供参考,不构成投资建议