feat: align with upstream 1.5.9.9

This commit is contained in:
2026-09-12 18:21:11 +08:00
commit 34f0679117
805 changed files with 376275 additions and 0 deletions
@@ -0,0 +1,481 @@
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