Files
BYW-VoiceHub/patches/0016-refactor-ci-uses-action-run-GitHub.patch
T

482 lines
21 KiB
Diff
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.
From cfd90865c858d7ed10996e19178af279dbfe2d77 Mon Sep 17 00:00:00 2001
From: HenryDu8133 <813367384@qq.com>
Date: Sun, 6 Sep 2026 11:20:52 +0800
Subject: [PATCH 16/58] =?UTF-8?q?refactor(ci):=20=E7=A7=BB=E9=99=A4?=
=?UTF-8?q?=E6=89=80=E6=9C=89=E7=AC=AC=E4=B8=89=E6=96=B9=20uses=20action?=
=?UTF-8?q?=EF=BC=8C=E6=94=B9=E7=94=A8=E7=BA=AF=20run=20=E5=91=BD=E4=BB=A4?=
=?UTF-8?q?=EF=BC=88=E8=A7=84=E9=81=BF=20GitHub=20=E4=B8=8B=E8=BD=BD?=
=?UTF-8?q?=E8=B6=85=E6=97=B6=EF=BC=89?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.gitea/workflows/deploy.yml | 404 ++++++++++++++++++++++--------------
1 file changed, 247 insertions(+), 157 deletions(-)
diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml
index d5355cf..386f982 100644
--- a/.gitea/workflows/deploy.yml
+++ b/.gitea/workflows/deploy.yml
@@ -34,19 +34,26 @@ jobs:
container:
image: node:22-bookworm-slim
steps:
- - name: 检出代码
- uses: actions/checkout@v4
- with:
- fetch-depth: 1
-
- - name: 运行时环境自检
- shell: bash
+ - name: 安装基础运行依赖(git/ca-certificates)
run: |
set -euxo pipefail
+ apt-get update -qq
+ DEBIAN_FRONTEND=noninteractive apt-get install -yqq --no-install-recommends \
+ git ca-certificates curl
+ rm -rf /var/lib/apt/lists/*
+ git --version
node -v
npm -v
- which node npm
- cat /etc/os-release 2>/dev/null | head -4 || true
+
+ - name: 检出代码
+ run: |
+ set -euxo pipefail
+ REPO_URL="https://${GITEA_HOST}/${GITHUB_REPOSITORY}.git"
+ BRANCH="${GITHUB_REF_NAME:-main}"
+ echo "Clone from: ${REPO_URL} (${BRANCH})"
+ rm -rf * .[^.]* 2>/dev/null || true
+ git clone --depth 1 -b "${BRANCH}" "${REPO_URL}" .
+ git rev-parse --short HEAD
- name: 安装依赖(npm ci)
run: |
@@ -84,13 +91,19 @@ jobs:
image_latest: ${{ steps.meta.outputs.latest }}
steps:
- name: 检出代码
- uses: actions/checkout@v4
- with:
- fetch-depth: 1
+ run: |
+ set -euxo pipefail
+ REPO_URL="https://${GITEA_HOST}/${GITHUB_REPOSITORY}.git"
+ BRANCH="${GITHUB_REF_NAME:-main}"
+ echo "Clone from: ${REPO_URL} (${BRANCH})"
+ rm -rf * .[^.]* 2>/dev/null || true
+ git clone --depth 1 -b "${BRANCH}" "${REPO_URL}" .
+ git rev-parse --short HEAD
+ docker version
+ docker buildx version || echo "buildx 未安装,将使用 docker build --push"
- name: 计算镜像标签
id: meta
- shell: bash
run: |
set -euo pipefail
SHA="${GITHUB_SHA:-$(git rev-parse HEAD)}"
@@ -107,54 +120,83 @@ jobs:
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: 初始化 Docker Buildx builder
+ run: |
+ set -euxo pipefail
+ BUILDER_NAME="multi"
+ # 如果已存在同名 builder,直接用;否则创建(默认 driver docker-container,支持 --cache-to registry)
+ if ! docker buildx inspect "${BUILDER_NAME}" >/dev/null 2>&1; then
+ docker buildx create --name "${BUILDER_NAME}" --driver docker-container --use \
+ --buildkitd-flags '--allow-insecure-entitlement security.insecure --allow-insecure-entitlement network.host'
+ else
+ docker buildx use "${BUILDER_NAME}"
+ fi
+ docker buildx inspect --bootstrap
- name: 登录 Gitea 容器仓库
- uses: docker/login-action@v3
- with:
- registry: ${{ env.GITEA_HOST }}
- username: ${{ secrets.REGISTRY_USER || github.actor }}
- password: ${{ secrets.REGISTRY_TOKEN || github.token }}
+ env:
+ REGISTRY_USER: ${{ secrets.REGISTRY_USER || github.actor }}
+ REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN || github.token }}
+ run: |
+ set -euo pipefail
+ if [ -z "${REGISTRY_TOKEN}" ]; then
+ echo "ERROR: REGISTRY_TOKEN 为空。请在 Repository Secrets 中新建 REGISTRY_TOKEN(权限:read:package + write:package)"
+ exit 1
+ fi
+ printf '%s' "${REGISTRY_TOKEN}" | docker login "${GITEA_HOST}" \
+ -u "${REGISTRY_USER}" --password-stdin
+ echo "登录成功:${REGISTRY_USER} @ ${GITEA_HOST}"
- 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 }}
+ SKIP_CACHE: ${{ inputs.skip_build_cache }}
+ IMG_FULL: ${{ steps.meta.outputs.full }}
+ IMG_LATEST: ${{ steps.meta.outputs.latest }}
+ OCS_SOURCE: ${{ github.server_url }}/${{ github.repository }}
+ OCS_REVISION: ${{ github.sha }}
+ OCS_CREATED: ${{ github.event.head_commit.timestamp }}
+ run: |
+ set -euxo pipefail
+
+ BUILD_ARGS=(
+ --build-arg BUILDKIT_INLINE_CACHE=1
+ --build-arg NODE_ENV=production
+ )
+ CACHE_ARGS=()
+ if [ "${SKIP_CACHE}" != "true" ]; then
+ CACHE_ARGS=(
+ --cache-from "type=registry,ref=${CACHE_REF}"
+ --cache-to "type=registry,ref=${CACHE_REF},mode=max"
+ )
+ else
+ echo "已启用 skip_build_cache,不使用分层缓存,强制重新构建"
+ BUILD_ARGS+=( --no-cache )
+ fi
+
+ docker buildx build \
+ --context . \
+ --file ./Dockerfile \
+ --platform linux/amd64 \
+ --push \
+ --provenance=false \
+ "${BUILD_ARGS[@]}" \
+ "${CACHE_ARGS[@]}" \
+ --tag "${IMG_FULL}" \
+ --tag "${IMG_LATEST}" \
+ --label "org.opencontainers.image.source=${OCS_SOURCE}" \
+ --label "org.opencontainers.image.revision=${OCS_REVISION}" \
+ --label "org.opencontainers.image.created=${OCS_CREATED}"
- name: 镜像体积与层信息
+ env:
+ IMG_FULL: ${{ steps.meta.outputs.full }}
run: |
set -euxo pipefail
- docker pull "${{ steps.meta.outputs.full }}" || true
- docker image inspect "${{ steps.meta.outputs.full }}" --format='{{.Size}}' 2>/dev/null \
+ docker pull "${IMG_FULL}" || true
+ docker image inspect "${IMG_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
+ docker history --no-trunc "${IMG_FULL}" 2>/dev/null | head -20 || true
deploy:
name: 部署到生产服务器
@@ -167,121 +209,169 @@ jobs:
url: ${{ secrets.NUXT_PUBLIC_SITE_URL }}
steps:
- name: 校验部署所需密钥
- shell: bash
+ env:
+ DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
+ DEPLOY_USER: ${{ secrets.DEPLOY_USER }}
+ DEPLOY_SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
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")
+ [ -n "${DEPLOY_HOST}" ] || MISSING+=("DEPLOY_HOST")
+ [ -n "${DEPLOY_USER}" ] || MISSING+=("DEPLOY_USER")
+ [ -n "${DEPLOY_SSH_KEY}" ] || MISSING+=("DEPLOY_SSH_KEY")
if [ ${#MISSING[@]} -gt 0 ]; then
echo "缺少以下 Repository Secrets:${MISSING[*]}"
- echo "请在 Gitea 仓库 -> 设置 -> 工作流 -> 密钥 页面添加"
+ echo "请在 Gitea 仓库 -> 设置 -> 工作流 -> 密钥 页面添加(注意是 Repository Secrets,不是 Deploy Keys)"
exit 1
fi
- echo "部署密钥配置完整"
+ echo "部署密钥配置完整(DEPLOY_HOST=${DEPLOY_HOST},DEPLOY_USER=${DEPLOY_USER})"
- 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
+ env:
+ DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
+ DEPLOY_PORT: ${{ secrets.DEPLOY_PORT || 22 }}
+ DEPLOY_USER: ${{ secrets.DEPLOY_USER }}
+ DEPLOY_SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
+ REGISTRY_PULL_USER: ${{ secrets.REGISTRY_PULL_USER }}
+ REGISTRY_PULL_TOKEN: ${{ secrets.REGISTRY_PULL_TOKEN }}
+ NUXT_PUBLIC_SITE_URL: ${{ secrets.NUXT_PUBLIC_SITE_URL }}
+ IMAGE_NEW: ${{ needs.build-image.outputs.image_full }}
+ IMAGE_LATEST: ${{ needs.build-image.outputs.image_latest }}
+ GITHUB_SERVER_URL: ${{ github.server_url }}
+ GITHUB_REPOSITORY: ${{ github.repository }}
+ GITHUB_REF_NAME: ${{ github.ref_name }}
+ run: |
+ set -euo pipefail
+
+ # 写私钥到临时文件(避免 stdin 转义问题)
+ SSH_KEY_FILE="$(mktemp -t deploy_ssh_key.XXXXXX)"
+ printf '%s\n' "${DEPLOY_SSH_KEY}" > "${SSH_KEY_FILE}"
+ chmod 600 "${SSH_KEY_FILE}"
+
+ SSH_OPTS=(
+ -i "${SSH_KEY_FILE}"
+ -o StrictHostKeyChecking=no
+ -o UserKnownHostsFile=/dev/null
+ -o ConnectTimeout=10
+ -o ServerAliveInterval=30
+ -o ServerAliveCountMax=8
+ -p "${DEPLOY_PORT}"
+ )
+
+ echo "=============================================="
+ echo "Deploy target: ${DEPLOY_USER}@${DEPLOY_HOST}:${DEPLOY_PORT}"
+ echo "Deploy image : ${IMAGE_NEW}"
+ echo "Deploy dir : ${DEPLOY_PATH}"
+ echo "=============================================="
+
+ # 本地预检查:ssh 先连上跑 whoami 确认连通,避免 20 分钟后才发现连不上
+ ssh "${SSH_OPTS[@]}" "${DEPLOY_USER}@${DEPLOY_HOST}" "whoami; docker --version; docker compose version"
+
+ # 把变量作为 heredoc 脚本的输入传过去,远端展开我们需要的值
+ ssh "${SSH_OPTS[@]}" "${DEPLOY_USER}@${DEPLOY_HOST}" \
+ IMAGE_NEW="${IMAGE_NEW}" \
+ IMAGE_LATEST="${IMAGE_LATEST}" \
+ DEPLOY_PATH="${DEPLOY_PATH}" \
+ GITEA_HOST="${GITEA_HOST}" \
+ REGISTRY_PULL_USER="${REGISTRY_PULL_USER}" \
+ REGISTRY_PULL_TOKEN="${REGISTRY_PULL_TOKEN}" \
+ IMAGE_NAME="${IMAGE_NAME}" \
+ NUXT_PUBLIC_SITE_URL="${NUXT_PUBLIC_SITE_URL}" \
+ GITHUB_SERVER_URL="${GITHUB_SERVER_URL}" \
+ GITHUB_REPOSITORY="${GITHUB_REPOSITORY}" \
+ GITHUB_REF_NAME="${GITHUB_REF_NAME}" \
+ bash <<'REMOTE_SCRIPT'
+ set -euxo pipefail
+
+ DEPLOY_DIR="${DEPLOY_PATH}"
+ COMPOSE="docker compose"
+
+ echo "=============================================="
+ echo "Executor : $(whoami)"
+ echo "Deploy image : ${IMAGE_NEW}"
+ echo "Deploy dir : ${DEPLOY_DIR}"
+ echo "=============================================="
+
+ mkdir -p "${DEPLOY_DIR}"
+ cd "${DEPLOY_DIR}"
+
+ if [ -n "${REGISTRY_PULL_TOKEN}" ] && [ -n "${REGISTRY_PULL_USER}" ]; then
+ echo "使用专用 pull 凭证登录 Gitea Container Registry"
+ printf '%s' "${REGISTRY_PULL_TOKEN}" \
+ | docker login "${GITEA_HOST}" \
+ -u "${REGISTRY_PULL_USER}" \
+ --password-stdin 2>/dev/null || true
fi
- if echo "${STATUS}" | grep -q '"State":"running"'; then
- :
- else
- echo "voicehub not running, current status:"
- ${COMPOSE} ps || true
+
+ 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}/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(参考仓库根目录 .env.example)"
+ 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 ..."
+ ${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
- 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 "================================================================="
+
+ 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: ${NUXT_PUBLIC_SITE_URL:-请自行查看你的站点 URL}"
+ echo "Rollback instructions (on server):"
+ echo " cd ${DEPLOY_DIR}"
+ echo " docker images | grep ${IMAGE_NAME} | head -5"
+ echo " printf 'services:\\n voicehub:\\n image: <OLD_TAG_FROM_ABOVE>\\n' > docker-compose.override.yml"
+ echo " docker compose up -d"
+ echo "================================================================="
+ REMOTE_SCRIPT
+
+ # 清理私钥临时文件
+ rm -f "${SSH_KEY_FILE}"
--
2.45.1.windows.1