d92e4fe31f
原 workflow_run 链触发会在 Actions 列表产生两条独立 run,UX 割裂。 合并后单 run + dependency graph 显式串联 build → deploy。 代价:失去"不 rebuild 只 redeploy"的 UI 单点触发,临时只想 重启容器需直接 ssh NAS 跑 docker compose up -d。 paths-ignore 同步移除已不存在的 deploy.yml 项。 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
168 lines
7.1 KiB
YAML
168 lines
7.1 KiB
YAML
name: Build Docker Image
|
||
|
||
on:
|
||
# 自动触发:push 到 custom 分支时跑(force-push 后的 rebase 也会触发,可接受)
|
||
# paths-ignore:纯文档/配置改动跳过,避免浪费 ~10 分钟构建
|
||
# ⚠️ 已知 quirk(2026-05-02 验证):empty commit(git commit --allow-empty)
|
||
# 不会触发 paths-ignore 过滤的 workflow,Gitea 把 zero-paths-changed 当作
|
||
# "vacuously matches ignore list" 跳过。要强制触发必须至少改一个非 ignore 路径
|
||
# 的真实文件(改这个 yml 自己最稳)。
|
||
push:
|
||
branches: [custom]
|
||
paths-ignore:
|
||
- '**.md'
|
||
- '.gitignore'
|
||
- 'LICENSE'
|
||
- 'screenshot/**'
|
||
# sync-upstream.yml 改的是 main reset 逻辑,跟 build 无关
|
||
# build-image.yml 自己留着会触发,作为 workflow 改动的 self-test
|
||
- '.gitea/workflows/sync-upstream.yml'
|
||
# 手动触发:保留作为应急通道(重新打包旧 commit / 用自定义 tag / 等)
|
||
# 注意:手动触发也会跑 deploy job —— 如果只想 build 不部署,临时把 deploy
|
||
# job 注释掉或在 deploy 里加 if 条件
|
||
workflow_dispatch:
|
||
inputs:
|
||
branch:
|
||
description: '要打包的分支(仅手动触发生效)'
|
||
required: true
|
||
default: 'custom'
|
||
tag:
|
||
description: '镜像 tag(留空则用 commit short hash)'
|
||
required: false
|
||
default: ''
|
||
|
||
# 并发控制:同一分支的连续 push 只跑最新的,旧 in-progress run 会被取消
|
||
# 例:连续 3 次 push,第 1 次 build 跑了 30s,第 2 次开始 → 取消第 1,第 2 跑;
|
||
# 期间第 3 次又来 → 取消第 2,第 3 跑。最后只构建+部署最新代码,省 CI 时间。
|
||
# group 包含 ref 是为了不同分支的 build 互不干扰(虽然当前只有 custom 用)
|
||
concurrency:
|
||
group: ${{ github.workflow }}-${{ github.ref }}
|
||
cancel-in-progress: true
|
||
|
||
jobs:
|
||
build:
|
||
runs-on: ubuntu-latest
|
||
steps:
|
||
- name: Checkout target branch
|
||
uses: actions/checkout@v4
|
||
with:
|
||
# workflow_dispatch 时用用户填的 branch;push 触发时 inputs.branch 为空,
|
||
# fallback 到 github.ref_name(即触发的分支名,push 到 custom 时就是 custom)
|
||
ref: ${{ inputs.branch || github.ref_name }}
|
||
fetch-depth: 0
|
||
|
||
- name: Set up Docker Buildx
|
||
uses: docker/setup-buildx-action@v3
|
||
with:
|
||
# 钉到 v0.13.2(自带 runc 1.1.x),避免 runc 1.2+ 的 procfs 安全检查
|
||
# 在 DSM 老内核(4.4.x)上撞 openat2/fsmount 不存在导致 build 失败
|
||
driver-opts: |
|
||
image=moby/buildkit:v0.13.2
|
||
|
||
- name: Login to Gitea Container Registry
|
||
uses: docker/login-action@v3
|
||
with:
|
||
registry: git.zhengchentao.win
|
||
username: ${{ gitea.actor }}
|
||
password: ${{ secrets.PACKAGES_TOKEN }}
|
||
|
||
- name: Determine image tag and revision
|
||
id: meta
|
||
run: |
|
||
if [ -n "${{ inputs.tag }}" ]; then
|
||
IMAGE_TAG="${{ inputs.tag }}"
|
||
else
|
||
IMAGE_TAG="$(git rev-parse --short HEAD)"
|
||
fi
|
||
echo "image_tag=$IMAGE_TAG" >> $GITHUB_OUTPUT
|
||
echo "full_sha=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT
|
||
echo "==> Image tag: $IMAGE_TAG"
|
||
|
||
- name: Build and push
|
||
uses: docker/build-push-action@v5
|
||
with:
|
||
context: .
|
||
push: true
|
||
# 上游 Dockerfile 用 BUILD_PIPELINE 作为 CI 跳过开关:
|
||
# 设为 "1" 时 pkg/exchangerates 跳过依赖第三方 API 的活测试
|
||
# (加拿大银行/乌兹别克央行 API 国内不稳,跑就超时)
|
||
# CHECK_3RD_API 留空 → 三方 API 测试不跑;想跑设 "1"
|
||
build-args: |
|
||
BUILD_PIPELINE=1
|
||
# OCI 标签:
|
||
# - source 让 Gitea 收包时自动把镜像关联到对应 repo(不再需要手动去
|
||
# "包设置 → 链接到仓库")
|
||
# - revision 把构建时的 commit full SHA 烙进镜像 manifest,
|
||
# docker inspect 能反推回源码版本
|
||
labels: |
|
||
org.opencontainers.image.source=https://git.zhengchentao.win/dev/ezbookkeeping
|
||
org.opencontainers.image.revision=${{ steps.meta.outputs.full_sha }}
|
||
tags: |
|
||
git.zhengchentao.win/dev/ezbookkeeping:${{ steps.meta.outputs.image_tag }}
|
||
git.zhengchentao.win/dev/ezbookkeeping:latest
|
||
|
||
- name: Build summary
|
||
# 把构建出的镜像 tag 与源 commit 显式列在 Action run summary 区,
|
||
# 方便从 UI 一眼看到本次 build 产出。always() 保证 build 失败也输出。
|
||
if: always()
|
||
run: |
|
||
{
|
||
echo "## Build Summary"
|
||
echo ""
|
||
echo "| 项 | 值 |"
|
||
echo "|---|---|"
|
||
echo "| 触发方式 | \`${{ github.event_name }}\` |"
|
||
echo "| 源分支 | \`${{ inputs.branch || github.ref_name }}\` |"
|
||
echo "| 源 commit (full) | \`${{ steps.meta.outputs.full_sha }}\` |"
|
||
echo "| 源 commit (short) | \`${{ steps.meta.outputs.image_tag }}\` |"
|
||
echo "| 镜像 tag | \`git.zhengchentao.win/dev/ezbookkeeping:${{ steps.meta.outputs.image_tag }}\` + \`:latest\` |"
|
||
} >> "$GITHUB_STEP_SUMMARY"
|
||
|
||
deploy:
|
||
# needs: build 串起来 —— build 失败 deploy 自动跳过,无需 if 条件
|
||
needs: build
|
||
runs-on: ubuntu-latest
|
||
steps:
|
||
# 登录 Gitea Container Registry,否则 docker compose pull 私有镜像 401。
|
||
# 跟 build job 那步是同一个 PACKAGES_TOKEN,但每个 job 跑在独立 runner 上,
|
||
# 凭据不会从 build job 继承,必须在这里再登一次。
|
||
- name: Login to Gitea Container Registry
|
||
uses: docker/login-action@v3
|
||
with:
|
||
registry: git.zhengchentao.win
|
||
username: ${{ gitea.actor }}
|
||
password: ${{ secrets.PACKAGES_TOKEN }}
|
||
|
||
- name: Pull and restart ezbookkeeping
|
||
# 部署逻辑直接内联在这。runner 容器挂了 host docker.sock,
|
||
# 所以这里 docker 命令直接操作的是宿主机 docker daemon,
|
||
# 容器层面相当于 "ssh 到 NAS 跑 docker compose"。
|
||
#
|
||
# NAS_INFRA_TOKEN secret 仅在 nas-infra 是私有仓库时需要;
|
||
# 公开仓库不设这个 secret 也能拉。
|
||
env:
|
||
NAS_INFRA_TOKEN: ${{ secrets.NAS_INFRA_TOKEN }}
|
||
run: |
|
||
set -e
|
||
|
||
TMPDIR=$(mktemp -d)
|
||
trap 'rm -rf "$TMPDIR"' EXIT
|
||
|
||
# 决定 clone URL:有 token 用 token(私有),没有用裸 URL(公开)
|
||
if [ -n "$NAS_INFRA_TOKEN" ]; then
|
||
CLONE_URL="https://x-access-token:${NAS_INFRA_TOKEN}@git.zhengchentao.win/dev/nas-infra.git"
|
||
else
|
||
CLONE_URL="https://git.zhengchentao.win/dev/nas-infra.git"
|
||
fi
|
||
|
||
git clone --depth 1 "$CLONE_URL" "$TMPDIR/nas-infra"
|
||
cd "$TMPDIR/nas-infra/ezbookkeeping"
|
||
|
||
docker compose pull
|
||
docker compose up -d
|
||
|
||
# 简单 health:列容器状态 + 输出最近日志
|
||
sleep 3
|
||
docker compose ps
|
||
docker compose logs --tail=30 ezbookkeeping
|