Files
BYW-VoiceHub/.gitea/workflows/deploy.yml
T
Henry_Du 10d363ca2b
构建并部署 BYW VoiceHub / Preflight 语法与构建检查 (push) Canceled after 4m27s
构建并部署 BYW VoiceHub / 构建并推送容器镜像 (push) Skipped
构建并部署 BYW VoiceHub / 部署到生产服务器 (push) Canceled after 5m33s
style(ci): 移除工作流步骤中的 emoji,改用专业风格命名
2026-09-06 10:35:56 +08:00

288 lines
11 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
name: 构建并部署 BYW VoiceHub
run-name: ${{ github.actor }} 触发部署 · Run ${{ github.run_number }}
on:
push:
branches: [main, master]
workflow_dispatch:
inputs:
deploy_target:
description: 构建完成后是否自动部署到服务器
type: boolean
default: true
required: false
skip_build_cache:
description: 跳过 BuildKit 分层缓存,强制重新构建(排错用)
type: boolean
default: false
required: false
concurrency:
group: deploy-production
cancel-in-progress: true
env:
GITEA_HOST: gitea.fse-media.group
DEPLOY_PATH: /data/opt/byw-voicehub
IMAGE_NAME: byw-voicehub
jobs:
preflight:
name: Preflight 语法与构建检查
runs-on: ubuntu-latest
timeout-minutes: 15
container:
image: node:22-bookworm-slim
steps:
- name: 检出代码
uses: actions/checkout@v4
with:
fetch-depth: 1
- name: 运行时环境自检
shell: bash
run: |
set -euxo pipefail
node -v
npm -v
which node npm
cat /etc/os-release 2>/dev/null | head -4 || true
- name: 安装依赖(npm ci)
run: |
set -euxo pipefail
npm config set registry https://registry.npmmirror.com || true
npm config set fund false
npm config set audit false
npm ci
- name: 检查 package.json 无 pnpm 残留
run: |
set -euo pipefail
if grep -qE '"packageManager"\s*:\s*"pnpm' package.json; then
echo "package.json 中发现 pnpm 包管理器声明,与要求冲突,构建中止"
exit 1
fi
echo "package.json 未发现 pnpm 声明"
- name: 执行 nuxt build
run: |
set -euxo pipefail
NODE_OPTIONS="--max-old-space-size=2048" npm run build
echo ".output/ 生成结果:"
ls -la .output/ 2>/dev/null || true
du -sh .output 2>/dev/null || true
build-image:
name: 构建并推送容器镜像
needs: preflight
runs-on: ubuntu-latest
timeout-minutes: 40
outputs:
image_tag: ${{ steps.meta.outputs.tag }}
image_full: ${{ steps.meta.outputs.full }}
image_latest: ${{ steps.meta.outputs.latest }}
steps:
- name: 检出代码
uses: actions/checkout@v4
with:
fetch-depth: 1
- name: 计算镜像标签
id: meta
shell: bash
run: |
set -euo pipefail
SHA="${GITHUB_SHA:-$(git rev-parse HEAD)}"
SHORT_SHA="${SHA:0:7}"
DATE_TAG="$(date +%Y%m%d)-${SHORT_SHA}"
IMG_FULL="${GITEA_HOST}/${GITHUB_REPOSITORY_OWNER}/${IMAGE_NAME}:${DATE_TAG}"
IMG_LATEST="${GITEA_HOST}/${GITHUB_REPOSITORY_OWNER}/${IMAGE_NAME}:latest"
echo "tag=${DATE_TAG}" >> "$GITHUB_OUTPUT"
echo "full=${IMG_FULL}" >> "$GITHUB_OUTPUT"
echo "latest=${IMG_LATEST}" >> "$GITHUB_OUTPUT"
echo "======================================"
echo "Image tag : ${DATE_TAG}"
echo "Full image : ${IMG_FULL}"
echo "Latest image : ${IMG_LATEST}"
echo "======================================"
- name: 启用 QEMU
uses: docker/setup-qemu-action@v3
with:
platforms: linux/amd64
- name: 启用 Docker Buildx
uses: docker/setup-buildx-action@v3
with:
install: true
- name: 登录 Gitea 容器仓库
uses: docker/login-action@v3
with:
registry: ${{ env.GITEA_HOST }}
username: ${{ secrets.REGISTRY_USER || github.actor }}
password: ${{ secrets.REGISTRY_TOKEN || github.token }}
- name: 构建并推送镜像
id: build_push
uses: docker/build-push-action@v6
env:
CACHE_REF: ${{ env.GITEA_HOST }}/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}:buildcache
with:
context: .
file: ./Dockerfile
platforms: linux/amd64
push: true
provenance: false
tags: |
${{ steps.meta.outputs.full }}
${{ steps.meta.outputs.latest }}
build-args: |
BUILDKIT_INLINE_CACHE=1
NODE_ENV=production
cache-from: ${{ inputs.skip_build_cache == true && '""' || format('type=registry,ref={0}', env.CACHE_REF) }}
cache-to: ${{ inputs.skip_build_cache == true && '""' || format('type=registry,ref={0},mode=max', env.CACHE_REF) }}
labels: |
org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }}
org.opencontainers.image.revision=${{ github.sha }}
org.opencontainers.image.created=${{ github.event.head_commit.timestamp }}
- name: 镜像体积与层信息
run: |
set -euxo pipefail
docker pull "${{ steps.meta.outputs.full }}" || true
docker image inspect "${{ steps.meta.outputs.full }}" --format='{{.Size}}' 2>/dev/null \
| awk '{printf "Image size: %.2f MB\n", $1/1024/1024}' || true
docker history --no-trunc "${{ steps.meta.outputs.full }}" 2>/dev/null | head -20 || true
deploy:
name: 部署到生产服务器
needs: build-image
if: ${{ !cancelled() && !failure() && (github.event_name == 'push' || inputs.deploy_target == true) }}
runs-on: ubuntu-latest
timeout-minutes: 25
environment:
name: production
url: ${{ secrets.NUXT_PUBLIC_SITE_URL }}
steps:
- name: 校验部署所需密钥
shell: bash
run: |
set -euo pipefail
MISSING=()
[ -n "${{ secrets.DEPLOY_HOST }}" ] || MISSING+=("DEPLOY_HOST")
[ -n "${{ secrets.DEPLOY_USER }}" ] || MISSING+=("DEPLOY_USER")
[ -n "${{ secrets.DEPLOY_SSH_KEY }}" ] || MISSING+=("DEPLOY_SSH_KEY")
if [ ${#MISSING[@]} -gt 0 ]; then
echo "缺少以下 Repository Secrets:${MISSING[*]}"
echo "请在 Gitea 仓库 -> 设置 -> 工作流 -> 密钥 页面添加"
exit 1
fi
echo "部署密钥配置完整"
- name: SSH 滚动部署
uses: appleboy/ssh-action@v1.2.0
with:
host: ${{ secrets.DEPLOY_HOST }}
port: ${{ secrets.DEPLOY_PORT || 22 }}
username: ${{ secrets.DEPLOY_USER }}
key: ${{ secrets.DEPLOY_SSH_KEY }}
command_timeout: 30m
script_stop: true
script: |
set -euxo pipefail
IMAGE_NEW="${{ needs.build-image.outputs.image_full }}"
IMAGE_LATEST="${{ needs.build-image.outputs.image_latest }}"
DEPLOY_DIR="${DEPLOY_PATH}"
COMPOSE="docker compose"
echo "=============================================="
echo "Deploy image : ${IMAGE_NEW}"
echo "Deploy dir : ${DEPLOY_DIR}"
echo "Executor : $(whoami)"
echo "=============================================="
mkdir -p "${DEPLOY_DIR}"
cd "${DEPLOY_DIR}"
if [ -n "${{ secrets.REGISTRY_PULL_TOKEN }}" ] && [ -n "${{ secrets.REGISTRY_PULL_USER }}" ]; then
echo "使用专用 pull 凭证登录 Gitea Container Registry"
echo "${{ secrets.REGISTRY_PULL_TOKEN }}" \
| docker login "${GITEA_HOST}" \
-u "${{ secrets.REGISTRY_PULL_USER }}" \
--password-stdin 2>/dev/null || true
fi
echo "Pulling new image ${IMAGE_NEW} ..."
docker pull "${IMAGE_NEW}"
docker tag "${IMAGE_NEW}" "${IMAGE_LATEST}"
echo "New image pulled and tagged as latest"
cat > docker-compose.override.yml <<YAML
services:
voicehub:
image: ${IMAGE_NEW}
YAML
echo "docker-compose.override.yml written, image=${IMAGE_NEW}"
if [ ! -f docker-compose.yml ]; then
echo "docker-compose.yml 不存在,自动从仓库模板下载"
TEMPLATE_URL="${GITHUB_SERVER_URL:-https://${GITEA_HOST}}/${GITHUB_REPOSITORY:-${GITHUB_REPOSITORY_OWNER}/${IMAGE_NAME}}/raw/branch/${GITHUB_REF_NAME:-main}/docker-compose.yml"
echo "URL=${TEMPLATE_URL}"
curl -fsSL --max-time 30 "${TEMPLATE_URL}" -o docker-compose.yml \
|| (
echo "自动下载模板失败,请手动上传 docker-compose.yml 和 .env"
)
fi
if [ ! -f .env ]; then
echo "================================================================="
echo "WARNING:${DEPLOY_DIR}/.env 不存在"
echo "首次部署请在服务器上创建 .env(至少填写 POSTGRES_PASSWORD / JWT_SECRET / NUXT_PUBLIC_SITE_URL)"
echo "参考仓库根目录的 .env.example"
echo "================================================================="
fi
echo "Executing docker compose up -d (voicehub only)..."
${COMPOSE} up -d --remove-orphans
MAX_WAIT=24
WAITED=0
echo "Waiting for container healthcheck (max 120s)..."
while [ ${WAITED} -lt ${MAX_WAIT} ]; do
STATUS="$(${COMPOSE} ps --format json 2>/dev/null || true)"
if echo "${STATUS}" | grep -q '"Health":"healthy"'; then
echo "voicehub healthcheck passed"
break
fi
if echo "${STATUS}" | grep -q '"State":"running"'; then
:
else
echo "voicehub not running, current status:"
${COMPOSE} ps || true
fi
sleep 5
WAITED=$((WAITED + 1))
done
if [ ${WAITED} -ge ${MAX_WAIT} ]; then
echo "Healthcheck timeout. Last 120 lines of voicehub logs:"
${COMPOSE} logs --tail=120 voicehub || true
exit 1
fi
echo "Pruning images older than 72 hours..."
docker image prune -af --filter "until=72h" || true
docker builder prune -af || true
echo ""
echo "================================================================="
echo "Deploy succeeded. Visit: ${{ secrets.NUXT_PUBLIC_SITE_URL || '请查看你的站点 URL' }}"
echo "Rollback instructions (on server):"
echo " cd ${DEPLOY_DIR}"
echo " docker images | grep ${IMAGE_NAME} | head -5"
echo " echo 'services:\n voicehub:\n image: ${GITEA_HOST}/${GITHUB_REPOSITORY_OWNER}/${IMAGE_NAME}:<OLD_TAG>' > docker-compose.override.yml"
echo " docker compose up -d"
echo "================================================================="