Skip to content

fix and update

fix and update #26

Workflow file for this run

# ============================================================
# THVote Result 前端独立部署流水线
# ============================================================
name: Build & Deploy Result Frontend
on:
push:
branches:
- zfq_dev_fe
paths:
- '.github/workflows/navigator-image.yml'
- '.github/workflows/result-image.yml'
- '.github/workflows/vote-image.yml'
- 'docker/**'
- 'packages/result/**'
- 'packages/shared/**'
- 'packages/result-codegen/**'
workflow_dispatch:
inputs:
environment:
description: '目标环境'
required: false
default: 'test'
type: choice
options:
- test
- prod
env:
REGISTRY: ghcr.io
OWNER: patchyvideo
IMAGE_NAME: thvote-result
jobs:
env-setup:
name: 确定环境配置
runs-on: ubuntu-latest
environment:
name: "${{ github.event_name == 'workflow_dispatch' && github.event.inputs.environment == 'prod' && 'prod' || 'test' }}"
outputs:
env: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.environment == 'prod' && 'prod' || 'test' }}
backend_dir: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.environment == 'prod' && (vars.PROD_SERVER_DIR || '/opt/thvote/prod') || (vars.TEST_SERVER_DIR || '/opt/thvote/test') }}
frontend_dir: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.environment == 'prod' && (vars.PROD_FRONTEND_SERVER_DIR || '/opt/thvote-fe/prod') || (vars.TEST_FRONTEND_SERVER_DIR || '/opt/thvote-fe/test') }}
ssh_ready: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.environment == 'prod' && 'true' || 'true' }}
steps:
- name: 输出当前 GitHub Environment
run: |
echo "GitHub Environment: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.environment == 'prod' && 'prod' || 'test' }}"
- name: 检测环境 & 输出所有变量
id: env-detect
shell: bash
run: |
if [[ "${{ github.event_name }}" == "workflow_dispatch" && "${{ github.event.inputs.environment }}" == "prod" ]]; then
ENV="prod"
GRAPHQL_URL="http://${{ secrets.PROD_SERVER_HOST }}:8000/graphql"
SERVER_HOST="${{ secrets.PROD_SERVER_HOST }}"
SERVER_USER="${{ secrets.PROD_SERVER_USER }}"
BACKEND_DIR="${{ vars.PROD_SERVER_DIR || '/opt/thvote/prod' }}"
FRONTEND_DIR="${{ vars.PROD_FRONTEND_SERVER_DIR || '/opt/thvote-fe/prod' }}"
SSH_KEY="${{ secrets.PROD_SERVER_SSH_KEY }}"
else
ENV="test"
GRAPHQL_URL="http://152.53.165.220:18000/graphql"
SERVER_HOST="${{ secrets.TEST_SERVER_HOST }}"
SERVER_USER="${{ secrets.TEST_SERVER_USER }}"
BACKEND_DIR="${{ vars.TEST_SERVER_DIR || '/opt/thvote/test' }}"
FRONTEND_DIR="${{ vars.TEST_FRONTEND_SERVER_DIR || '/opt/thvote-fe/test' }}"
SSH_KEY="${{ secrets.TEST_SERVER_SSH_KEY }}"
fi
echo "env=$ENV" >> $GITHUB_OUTPUT
echo "graphql_url=$GRAPHQL_URL" >> $GITHUB_OUTPUT
echo "backend_dir=$BACKEND_DIR" >> $GITHUB_OUTPUT
echo "frontend_dir=$FRONTEND_DIR" >> $GITHUB_OUTPUT
echo "server_host=$SERVER_HOST" >> $GITHUB_OUTPUT
echo "server_user=$SERVER_USER" >> $GITHUB_OUTPUT
if [[ -n "$SERVER_HOST" && -n "$SERVER_USER" && -n "$SSH_KEY" ]]; then
echo "ssh_ready=true" >> $GITHUB_OUTPUT
else
echo "ssh_ready=false" >> $GITHUB_OUTPUT
fi
backend-ready:
name: 确保后端服务就绪
runs-on: ubuntu-latest
environment:
name: "${{ needs.env-setup.outputs.env }}"
needs: env-setup
outputs:
graphql_url: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.environment == 'prod' && format('http://{0}:8000/graphql', secrets.PROD_SERVER_HOST) || 'http://152.53.165.220:18000/graphql' }}
steps:
- name: 输出 SSH 检查结果
run: |
if [[ "${{ needs.env-setup.outputs.ssh_ready }}" == "true" ]]; then
echo "SSH 配置完整,执行远端后端健康检查"
else
echo "SSH 配置缺失,跳过远端后端检查,直接使用 GRAPHQL_SCHEMA_URL=http://152.53.165.220:18000/graphql"
fi
- name: 确保后端服务运行
if: needs.env-setup.outputs.ssh_ready == 'true'
uses: appleboy/ssh-action@v1.2.5
with:
host: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.environment == 'prod' && secrets.PROD_SERVER_HOST || secrets.TEST_SERVER_HOST }}
username: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.environment == 'prod' && secrets.PROD_SERVER_USER || secrets.TEST_SERVER_USER }}
key: ${{ needs.env-setup.outputs.env == 'prod' && secrets.PROD_SERVER_SSH_KEY || secrets.TEST_SERVER_SSH_KEY }}
script: |
set -e
BACKEND_DIR="${{ needs.env-setup.outputs.backend_dir }}"
cd "$BACKEND_DIR"
echo ${{ secrets.GITHUB_TOKEN }} | docker login ${{ env.REGISTRY }} -u ${{ github.actor }} --password-stdin || true
docker-compose pull backend || true
docker-compose up -d backend
- name: 等待后端健康检查
if: needs.env-setup.outputs.ssh_ready == 'true'
uses: appleboy/ssh-action@v1.2.5
with:
host: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.environment == 'prod' && secrets.PROD_SERVER_HOST || secrets.TEST_SERVER_HOST }}
username: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.environment == 'prod' && secrets.PROD_SERVER_USER || secrets.TEST_SERVER_USER }}
key: ${{ needs.env-setup.outputs.env == 'prod' && secrets.PROD_SERVER_SSH_KEY || secrets.TEST_SERVER_SSH_KEY }}
script: |
set -e
BACKEND_DIR="${{ needs.env-setup.outputs.backend_dir }}"
cd "$BACKEND_DIR"
for i in $(seq 1 60); do
if docker-compose exec -T backend curl -sf http://localhost:8000/health > /dev/null 2>&1; then
echo "后端就绪"
exit 0
fi
echo "等待后端... ($i/60)"
sleep 3
done
docker-compose logs backend | tail -20
exit 1
- name: 验证 GraphQL schema 端点
if: needs.env-setup.outputs.ssh_ready == 'true'
uses: appleboy/ssh-action@v1.2.5
with:
host: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.environment == 'prod' && secrets.PROD_SERVER_HOST || secrets.TEST_SERVER_HOST }}
username: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.environment == 'prod' && secrets.PROD_SERVER_USER || secrets.TEST_SERVER_USER }}
key: ${{ needs.env-setup.outputs.env == 'prod' && secrets.PROD_SERVER_SSH_KEY || secrets.TEST_SERVER_SSH_KEY }}
script: |
set -e
BACKEND_DIR="${{ needs.env-setup.outputs.backend_dir }}"
cd "$BACKEND_DIR"
SCHEMA_OUTPUT=$(docker-compose exec -T backend curl -sf http://localhost:8000/graphql/schema 2>&1 | head -c 200)
if [ -n "$SCHEMA_OUTPUT" ]; then
echo "GraphQL schema 端点正常"
else
SCHEMA_CHECK=$(docker-compose exec -T backend curl -sf -X POST http://localhost:8000/graphql \
-H "Content-Type: application/json" \
-d '{"query":"{ __schema { queryType { name } } }"}' 2>&1)
if ! echo "$SCHEMA_CHECK" | grep -q "queryType"; then
echo "警告:无法获取 GraphQL schema"
fi
fi
build:
name: 构建 Result 镜像
runs-on: ubuntu-latest
environment:
name: "${{ needs.env-setup.outputs.env }}"
needs: [env-setup, backend-ready]
permissions:
contents: read
packages: write
steps:
- name: 检出代码
uses: actions/checkout@v4
- name: 设置 pnpm
uses: pnpm/action-setup@v4
with:
version: 7.33.7
- name: 设置 Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'pnpm'
- name: 安装依赖
run: pnpm install --frozen-lockfile
- name: Codegen(从后端获取 schema)
env:
GRAPHQL_SCHEMA_URL: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.environment == 'prod' && format('http://{0}:8000/graphql', secrets.PROD_SERVER_HOST) || 'http://152.53.165.220:18000/graphql' }}
run: |
echo "=== Codegen: schema from $GRAPHQL_SCHEMA_URL ==="
curl -sf "$GRAPHQL_SCHEMA_URL" -o /dev/null
pnpm --filter @touhou-vote/result run codegen
- name: 构建 Result 应用
run: pnpm --filter @touhou-vote/result run build -- --mode test
- name: 登录容器仓库
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ env.OWNER }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: 提取镜像版本
id: meta
run: |
if [[ "${{ github.event_name }}" == "workflow_dispatch" && "${{ github.event.inputs.environment }}" == "prod" ]]; then
VERSION="prod-$(date +%Y%m%d)-$(git rev-parse --short HEAD)"
elif [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
VERSION="test-$(date +%Y%m%d-%H%M%S)"
else
VERSION="test-$(date +%Y%m%d)-$(git rev-parse --short HEAD)"
fi
echo "version=$VERSION" >> $GITHUB_OUTPUT
- name: 构建并推送镜像
run: |
cd packages/result/dist
VERSION_TAG="${{ env.REGISTRY }}/${{ env.OWNER }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }}"
TEST_TAG="${{ env.REGISTRY }}/${{ env.OWNER }}/${{ env.IMAGE_NAME }}:test"
PROD_TAG="${{ env.REGISTRY }}/${{ env.OWNER }}/${{ env.IMAGE_NAME }}:prod"
docker build -f - -t $VERSION_TAG -t $TEST_TAG -t $PROD_TAG . << 'DOCKEREOF'
FROM nginx:alpine
RUN echo 'server { \
listen 8084; \
location / { \
root /usr/share/nginx/html; \
try_files $uri $uri/ /index.html; \
} \
location /res-be/ { \
proxy_pass http://thvote-backend:8000/; \
proxy_http_version 1.1; \
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; \
} \
}' > /etc/nginx/conf.d/default.conf
EXPOSE 8084
CMD ["nginx", "-g", "daemon off;"]
COPY . /usr/share/nginx/html
DOCKEREOF
docker push $VERSION_TAG
docker push $TEST_TAG
docker push $PROD_TAG
- name: 部署到目标服务器
if: needs.env-setup.outputs.ssh_ready == 'true'
uses: appleboy/ssh-action@v1.2.5
with:
host: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.environment == 'prod' && secrets.PROD_SERVER_HOST || secrets.TEST_SERVER_HOST }}
username: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.environment == 'prod' && secrets.PROD_SERVER_USER || secrets.TEST_SERVER_USER }}
key: ${{ needs.env-setup.outputs.env == 'prod' && secrets.PROD_SERVER_SSH_KEY || secrets.TEST_SERVER_SSH_KEY }}
script: |
set -e
FRONTEND_DIR="${{ needs.env-setup.outputs.frontend_dir }}"
mkdir -p "$FRONTEND_DIR"
cd "$FRONTEND_DIR"
echo ${{ secrets.GITHUB_TOKEN }} | docker login ${{ env.REGISTRY }} -u ${{ github.actor }} --password-stdin || true
cat > docker-compose.yml << 'COMPOSEEOF'
version: "3.8"
x-frontend-common: &frontend_common
restart: unless-stopped
networks:
- thvote-net
pull_policy: always
services:
vote:
<<: *frontend_common
image: ${VOTE_IMAGE:-ghcr.io/patchyvideo/thvote-vote:test}
container_name: thvote-vote
ports:
- "${VOTE_PORT:-8082}:8082"
navigator:
<<: *frontend_common
image: ${NAVIGATOR_IMAGE:-ghcr.io/patchyvideo/thvote-navigator:test}
container_name: thvote-navigator
ports:
- "${NAVIGATOR_PORT:-8083}:8083"
result:
<<: *frontend_common
image: ${RESULT_IMAGE:-ghcr.io/patchyvideo/thvote-result:test}
container_name: thvote-result
ports:
- "${RESULT_PORT:-8084}:8084"
networks:
thvote-net:
name: thvote-net
external: true
COMPOSEEOF
ENV_FILE=".env.${{ needs.env-setup.outputs.env }}"
if [ ! -f "$ENV_FILE" ]; then
cat > "$ENV_FILE" << 'ENVEOF'
VOTE_IMAGE=ghcr.io/patchyvideo/thvote-vote:${{ needs.env-setup.outputs.env }}
NAVIGATOR_IMAGE=ghcr.io/patchyvideo/thvote-navigator:${{ needs.env-setup.outputs.env }}
RESULT_IMAGE=ghcr.io/patchyvideo/thvote-result:${{ needs.env-setup.outputs.env }}
VOTE_PORT=8082
NAVIGATOR_PORT=8083
RESULT_PORT=8084
ENVEOF
fi
cp "$ENV_FILE" .env
sed -i.bak "s|^RESULT_IMAGE=.*|RESULT_IMAGE=${{ env.REGISTRY }}/${{ env.OWNER }}/${{ env.IMAGE_NAME }}:${{ needs.env-setup.outputs.env }}|" .env
rm -f .env.bak
docker pull "${{ env.REGISTRY }}/${{ env.OWNER }}/${{ env.IMAGE_NAME }}:${{ needs.env-setup.outputs.env }}" || true
docker-compose up -d --no-deps result
docker image prune -f
- name: 总结
run: |
echo "## Result 镜像构建完成" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| 项目 | 值 |" >> $GITHUB_STEP_SUMMARY
echo "|------|----|" >> $GITHUB_STEP_SUMMARY
echo "| 镜像 | ${{ env.REGISTRY }}/${{ env.OWNER }}/${{ env.IMAGE_NAME }} |" >> $GITHUB_STEP_SUMMARY
echo "| Tag | ${{ steps.meta.outputs.version }} |" >> $GITHUB_STEP_SUMMARY
echo "| 环境 | ${{ needs.env-setup.outputs.env }} |" >> $GITHUB_STEP_SUMMARY
echo "| Schema 来源 | ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.environment == 'prod' && format('http://{0}:8000/graphql', secrets.PROD_SERVER_HOST) || 'http://152.53.165.220:18000/graphql' }} |" >> $GITHUB_STEP_SUMMARY
echo "| SSH 部署 | ${{ needs.env-setup.outputs.ssh_ready == 'true' && 'enabled' || 'skipped' }} |" >> $GITHUB_STEP_SUMMARY