From 0378504be0b9a22b0285679421dfd3c5346ff4b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AF=92=E5=AF=92?= <2596194220@qq.com> Date: Tue, 30 Jun 2026 23:27:34 +0800 Subject: [PATCH] Add Gitea Docker package workflow --- .gitea/workflows/docker.yml | 143 ++++++++++++++++++++++++++---------- Dockerfile | 14 +++- 2 files changed, 115 insertions(+), 42 deletions(-) diff --git a/.gitea/workflows/docker.yml b/.gitea/workflows/docker.yml index 38d99da..45d2107 100644 --- a/.gitea/workflows/docker.yml +++ b/.gitea/workflows/docker.yml @@ -3,89 +3,154 @@ name: Build Docker Package on: push: branches: - - master + - main tags: - "v*" workflow_dispatch: jobs: docker-package: - runs-on: ubuntu-latest + runs-on: gitea_act + # 使用 Docker CLI 镜像,因为 runner 默认任务镜像不一定包含 docker、 + # bash 或基于 Node 的 action 依赖。 + container: + image: docker:28-cli env: REGISTRY: git.hanhan.ltd + GITEA_SERVER_URL: https://git.hanhan.ltd NODE_IMAGE: m.daocloud.io/docker.io/library/node:22 NPM_REGISTRY: https://registry.npmmirror.com steps: + - name: Install job tools + shell: sh + run: | + apk add --no-cache git + docker --version + git --version + - name: Checkout - uses: actions/checkout@v4 + env: + GITEA_ACTOR: ${{ gitea.actor }} + GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} + shell: sh + run: | + command -v git + + repository="${GITHUB_REPOSITORY:-${{ gitea.repository }}}" + # GITHUB_SERVER_URL 可能指向任务容器无法解析的内部容器主机名, + # 因此这里固定使用公网 Gitea 地址。 + server_url="${GITEA_SERVER_URL}" + sha="${GITHUB_SHA:-${{ gitea.sha }}}" + + git init . + git remote add origin "${server_url}/${repository}.git" + + if [ -n "${GITEA_TOKEN}" ]; then + auth="$(printf '%s:%s' "${GITEA_ACTOR}" "${GITEA_TOKEN}" | base64 | tr -d '\n')" + git -c "http.extraHeader=Authorization: Basic ${auth}" fetch --depth=1 origin "${sha}" + else + git fetch --depth=1 origin "${sha}" + fi + + git checkout --force FETCH_HEAD + git config --global --add safe.directory "$PWD" - name: Prepare image metadata - id: meta - shell: bash + shell: sh run: | repository="${GITHUB_REPOSITORY:-${{ gitea.repository }}}" - repository="$(printf '%s' "$repository" | tr '[:upper:]' '[:lower:]')" - image="${REGISTRY}/${repository}" + image_repository="$(printf '%s' "$repository" | tr '[:upper:]' '[:lower:]')" + image="${REGISTRY}/${image_repository}" sha="${GITHUB_SHA:-${{ gitea.sha }}}" - short_sha="${sha:0:12}" ref="${GITHUB_REF:-${{ gitea.ref }}}" + source_url="${GITEA_SERVER_URL}/${repository}" + + : > .image-tags + + # 分支构建发布滚动的 latest 标签;版本标签如 v1.2.3 + # 只发布不可变的 1.2.3 镜像标签。 + case "${ref}" in + refs/tags/v*) + tag="${ref#refs/tags/}" + version="${tag#v}" + echo "${image}:${version}" >> .image-tags + ;; + *) + echo "${image}:latest" >> .image-tags + ;; + esac { - echo "image=${image}" - echo "tags< .image-labels - if [[ "${ref}" == "refs/heads/master" ]]; then - echo "${image}:latest" - fi - - if [[ "${ref}" == refs/tags/* ]]; then - tag="${ref#refs/tags/}" - echo "${image}:${tag}" - fi - - echo "EOF" - } >> "${GITHUB_OUTPUT}" + cat .image-tags - name: Login to Gitea container registry env: PACKAGE_USERNAME: ${{ secrets.PACKAGE_USERNAME }} PACKAGE_TOKEN: ${{ secrets.PACKAGE_TOKEN }} GITEA_ACTOR: ${{ gitea.actor }} - shell: bash + shell: sh run: | + if [ -z "${PACKAGE_TOKEN}" ]; then + echo "PACKAGE_TOKEN secret is required to publish container packages." >&2 + echo "Create a Gitea personal access token with package write permission for PACKAGE_USERNAME." >&2 + exit 1 + fi username="${PACKAGE_USERNAME:-${GITEA_ACTOR}}" - if [[ -z "${PACKAGE_TOKEN}" ]]; then - echo "PACKAGE_TOKEN secret is required to publish container packages." >&2 - echo "Create a Gitea personal access token with package write permission and save it as PACKAGE_TOKEN." >&2 + # PACKAGE_USERNAME 为了方便可以不填;但当 token 所属用户和 + # 触发流水线的用户不一致时,显式设置它可以避免 unauthorized。 + if [ -z "${username}" ]; then + echo "PACKAGE_USERNAME secret is required because Gitea actor is empty." >&2 + echo "Set it to the Gitea username that owns PACKAGE_TOKEN." >&2 + exit 1 + fi + + if [ -z "${PACKAGE_USERNAME}" ]; then + echo "PACKAGE_USERNAME is not set; using Gitea actor '${username}' for docker login." + echo "If login fails with unauthorized, set PACKAGE_USERNAME to the token owner." + fi + + if ! docker version; then + echo "Docker daemon is not reachable from the Gitea runner." >&2 + echo "Mount /var/run/docker.sock into the runner or use a dind runner image." >&2 exit 1 fi echo "${PACKAGE_TOKEN}" | docker login "${REGISTRY}" -u "${username}" --password-stdin - name: Build image - shell: bash + shell: sh run: | - mapfile -t tags <<< "${{ steps.meta.outputs.tags }}" + tag_args="" + while IFS= read -r tag; do + tag_args="${tag_args} --tag ${tag}" + done < .image-tags - args=() - for tag in "${tags[@]}"; do - args+=(--tag "$tag") - done + label_args="" + while IFS= read -r label; do + label_args="${label_args} --label ${label}" + done < .image-labels - docker build \ + # tag/label 参数都由上面的脚本生成,值里不包含空格; + # 这里保留拆词是为了把它们展开成 docker build 参数。 + # shellcheck disable=SC2086 + docker build $tag_args $label_args \ --build-arg "NODE_IMAGE=${NODE_IMAGE}" \ --build-arg "NPM_REGISTRY=${NPM_REGISTRY}" \ - "${args[@]}" \ . - name: Push image - shell: bash + shell: sh run: | - mapfile -t tags <<< "${{ steps.meta.outputs.tags }}" - - for tag in "${tags[@]}"; do + while IFS= read -r tag; do docker push "$tag" - done + done < .image-tags diff --git a/Dockerfile b/Dockerfile index 4f28559..d40b049 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,11 +3,16 @@ ARG NPM_REGISTRY=https://registry.npmmirror.com FROM ${NODE_IMAGE} AS deps WORKDIR /app +# FROM 之前声明的 ARG 不会自动进入每个构建阶段; +# 只要 RUN 命令里要使用 registry,就需要在当前阶段重新声明。 +ARG NPM_REGISTRY=https://registry.npmmirror.com COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./ +# 仓库中提交的 lockfile 是本次构建的可信输入; +# 这里避免 pnpm 11 在 Docker 构建期间重新用 registry 元数据校验供应链策略。 RUN corepack enable \ && pnpm config set registry ${NPM_REGISTRY} \ - && pnpm install --frozen-lockfile + && pnpm install --frozen-lockfile --config.trust-lockfile=true FROM ${NODE_IMAGE} AS builder WORKDIR /app @@ -15,8 +20,10 @@ ENV NEXT_TELEMETRY_DISABLED=1 COPY --from=deps /app/node_modules ./node_modules COPY . . +# 每个 Docker 阶段都有独立 PATH,因此使用 pnpm 前需要重新启用 corepack。 +RUN corepack enable RUN mkdir -p public/audio -RUN npm run build +RUN pnpm build FROM ${NODE_IMAGE} AS runner WORKDIR /app @@ -29,9 +36,10 @@ ENV PORT=3000 ENV AUDIO_DIRECTORY=/app/public/audio COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./ +# 运行时镜像只安装生产依赖。 RUN corepack enable \ && pnpm config set registry ${NPM_REGISTRY} \ - && pnpm install --prod --frozen-lockfile \ + && pnpm install --prod --frozen-lockfile --config.trust-lockfile=true \ && pnpm store prune COPY --from=builder /app/.next ./.next