c99332d770
build / build (push) Successful in 49s
为什么要改:通知里的链接一直用 GITHUB_SERVER_URL 拼(内网地址),群里/换网就点不开。 做法(不覆盖 GITHUB_SERVER_URL、不碰检出与内部调用): - ci_report.py 新增 _link_base():CI_PUBLIC_BASE_URL 优先,缺/非法(非 http(s)、带 userinfo、带空白)⇒ 明确回退 GITHUB_SERVER_URL,并把来源写进 payload 的 link_base_source(可复盘)。 - 三类链接统一用它:run_url、commit_url(新增,提交行用短 SHA 展示、完整 SHA 在链接里)、compare_url;仓库必须是真 owner/repo、SHA 必须是完整 40 位,否则宁可不给链接。 - workflow 上报步骤加非秘密 CI_PUBLIC_BASE_URL: https://gitea.zheromiku.top(写在 YAML 里便于审查)。 - **不做消息文本的全局 URL 替换**:提交标题/摘要里的 URL 仍是文本(bridge 侧负例钉住)。 - 自检补 _link_base/_commit_url 分支(缺失回退、非法回退、尾斜杠、短 SHA 不拼链接)。
199 lines
11 KiB
YAML
199 lines
11 KiB
YAML
name: build
|
||
|
||
# 提交 → 本机 Runner 跑**唯一权威门禁**(make verify)→ 全部必需检查通过才发预览 → 通知
|
||
#
|
||
# ⚠️ 本 workflow **只做 CI,不做 CD**:这里没有 deploy:test / deploy-production,
|
||
# 也不启动 compose。部署是各项目自己的事(正本 §8:通用 CI 默认不生成部署作业)。
|
||
#
|
||
# ⚠️ 凭据处理(三条硬规矩):
|
||
# 1. **不把令牌写进 remote URL**:URL 里的凭据会落进 .git/config,还会出现在任何打印 remote
|
||
# 的地方;这里用 credential helper(只在那一次 git 调用里生效)。
|
||
# 2. **不把密钥放在 workflow 级 env**:那等于把 bridge 令牌发给每一个步骤(包括跑待测代码的
|
||
# 步骤)。谁需要谁在自己的步骤上声明 —— 见「静态预览」「上报 ci-bridge」。
|
||
# 3. **不用写权限的令牌**:检出只用 GITHUB_TOKEN(只读),不请求任何写操作。
|
||
# `permissions:` 在 Gitea 1.26.2 上按实例支持情况而定(不支持时会被忽略)。
|
||
#
|
||
# 实测结论(勿随意改动):
|
||
# 1. 不用任何第三方 action(本 runner 的 github_mirror 为空,取 action 会随机红)
|
||
# 2. 必须自己 clone —— Runner 不会自动检出代码,工作区初始为空(trap-6)
|
||
# 3. act 以 `bash -e -o pipefail` 执行每步 → 禁止 `cmd | head` 这种会 SIGPIPE 提前退出的管道
|
||
# 4. 工具链来自主机只读挂载 /opt/citools(go/uv),缓存写 /opt/citools-cache
|
||
# 5. bridge 从容器内走 docker 网关 172.17.0.1:8899(实测可达)
|
||
# 6. 校验逻辑放仓内脚本(scripts/ci_checks.py + .gitea/scripts/*.py),YAML 只负责调用
|
||
# 7. 检出固定到**事件完整 SHA**,不是"当时那个分支 HEAD"(分支随时会前进)
|
||
on:
|
||
push:
|
||
branches:
|
||
- "**"
|
||
workflow_dispatch:
|
||
|
||
env:
|
||
LANG: C.UTF-8
|
||
LC_ALL: C.UTF-8
|
||
|
||
permissions:
|
||
contents: read
|
||
|
||
# 同一 ref 的新推送取消旧的运行。本 workflow 不含部署,取消它不会动任何线上东西;
|
||
# 生产部署是另一个 workflow,那种必须 cancel-in-progress: false —— 取消不是回滚。
|
||
concurrency:
|
||
group: build-${{ github.ref }}
|
||
cancel-in-progress: true
|
||
|
||
jobs:
|
||
build:
|
||
runs-on: ubuntu-latest
|
||
timeout-minutes: 40
|
||
steps:
|
||
- name: 检出代码(最小 bootstrap:不依赖仓内文件,固定事件完整 SHA)
|
||
env:
|
||
GIT_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||
run: |
|
||
set -uo pipefail
|
||
# ⚠️ 工作区初始是空的(本 Runner 不自动检出,trap-6)⇒ 这一步**只能用 runner 给的环境变量**,
|
||
# 绝不能调用仓内脚本:此时 .gitea/scripts/ 还不存在,实测 rc=127(第三轮审查的启动阻断)。
|
||
# 检出完成、仓内脚本就位之后,才交给 ci_checkout.sh --post-checkout 做再断言与补取。
|
||
SRV="${GITHUB_SERVER_URL:-http://192.168.50.223:3000}"
|
||
case "$SRV" in
|
||
https://*|http://*) ;;
|
||
*) echo "❌ GITHUB_SERVER_URL 的协议不认识(只接受 http/https):${SRV:0:40}"; exit 1 ;;
|
||
esac
|
||
case "$SRV" in *[[:space:]]*) echo "❌ GITHUB_SERVER_URL 里有空白字符"; exit 1 ;; esac
|
||
REMOTE="${SRV%/}/${GITHUB_REPOSITORY}.git"
|
||
case "$REMOTE" in
|
||
https://*) SCHEME="https" ;;
|
||
http://*) SCHEME="http" ;;
|
||
*) echo "❌ remote 的协议不对:${REMOTE:0:30}"; exit 1 ;;
|
||
esac
|
||
WANT="${SRV%%://*}"
|
||
[ "$SCHEME" = "$WANT" ] || { echo "❌ 协议被降级:$SCHEME(服务端给的是 $WANT)"; exit 1; }
|
||
case "$REMOTE" in *@*) echo "❌ remote URL 里带上了凭据"; exit 1 ;; esac
|
||
[ -n "${GITHUB_SHA:-}" ] || { echo "❌ 没有 GITHUB_SHA:检出必须固定到事件的完整 SHA"; exit 1; }
|
||
[ -n "${GIT_TOKEN:-}" ] || { echo "❌ 没拿到读仓库用的令牌(secrets.GITHUB_TOKEN 为空)"; exit 1; }
|
||
BR="${GITHUB_REF_NAME:-${GITHUB_REF:-}}"; BR="${BR#refs/heads/}"
|
||
cd "${GITHUB_WORKSPACE:-$(pwd)}"
|
||
rm -rf ./* .git 2>/dev/null || true
|
||
git init -q .
|
||
git remote add origin "$REMOTE"
|
||
ga() { git -c credential.helper='!f() { printf "username=x-access-token\npassword=%s\n" "$GIT_TOKEN"; }; f' "$@"; }
|
||
if [ -n "$BR" ]; then
|
||
ga fetch -q --depth 50 origin "refs/heads/$BR" || { echo "❌ 取不到分支 $BR"; exit 1; }
|
||
else
|
||
ga fetch -q --depth 50 origin "$GITHUB_SHA" || { echo "❌ 取不到事件 SHA $GITHUB_SHA"; exit 1; }
|
||
fi
|
||
git checkout -q --detach FETCH_HEAD
|
||
if [ "$(git rev-parse HEAD)" != "$GITHUB_SHA" ]; then
|
||
echo "⚠️ 分支 $BR 的 HEAD != 事件 SHA(有人在之后又推了一次)—— 在已取到的历史里找事件 SHA"
|
||
# ⚠️ 不要 `fetch origin $SHA`:默认 Gitea/upload-pack **不允许请求未公开的对象**
|
||
# (实测 "服务器不允许请求未公开的对象")。做法是取深一点再核。
|
||
if ! git cat-file -e "${GITHUB_SHA}^{commit}" 2>/dev/null; then
|
||
ga fetch -q --deepen 200 origin "refs/heads/$BR" 2>/dev/null \
|
||
|| ga fetch -q --depth 200 origin "refs/heads/$BR" 2>/dev/null || true
|
||
fi
|
||
if git cat-file -e "${GITHUB_SHA}^{commit}" 2>/dev/null; then
|
||
git checkout -q --detach "$GITHUB_SHA"
|
||
else
|
||
echo "❌ 在已取到的历史里找不到事件 SHA $GITHUB_SHA"
|
||
echo " (浅克隆窗口不够:提高 fetch 深度;或等这次推送的 run 重跑)"
|
||
exit 1
|
||
fi
|
||
fi
|
||
GOT_SHA="$(git rev-parse HEAD)"
|
||
[ "$GOT_SHA" = "$GITHUB_SHA" ] || { echo "❌ 检出 $GOT_SHA != 事件 SHA $GITHUB_SHA"; exit 1; }
|
||
echo "✅ bootstrap 检出 ${GOT_SHA:0:12}(== 事件完整 SHA,协议 $SCHEME,remote 不含凭据)"
|
||
# 检出完成 ⇒ 仓内脚本就位:交给它做协议/身份再断言,并补取 before 与默认分支引用
|
||
GITHUB_TOKEN="$GIT_TOKEN" GITHUB_ENV="${GITHUB_ENV:-/dev/null}" \
|
||
bash .gitea/scripts/ci_checkout.sh --post-checkout
|
||
|
||
- name: 环境与工具链(缺什么一次性报全)
|
||
run: |
|
||
set -uo pipefail
|
||
{
|
||
echo "PATH=/opt/citools/go/bin:/opt/citools/bin:$PATH"
|
||
echo "GOCACHE=/opt/citools-cache/gocache"
|
||
echo "GOMODCACHE=/opt/citools-cache/gomod"
|
||
echo "GOPATH=/opt/citools-cache/gopath"
|
||
echo "UV_CACHE_DIR=/opt/citools-cache/uv"
|
||
echo "GOPROXY=https://goproxy.cn,direct"
|
||
echo "GOTOOLCHAIN=local"
|
||
echo "npm_config_cache=/opt/citools-cache/npm"
|
||
echo "CI_GATE_LOG=${GITHUB_WORKSPACE}/ci-gate.log"
|
||
echo "CI_REPORT=${GITHUB_WORKSPACE}/ci-report.json"
|
||
} >> "$GITHUB_ENV"
|
||
mkdir -p /opt/citools-cache/gocache /opt/citools-cache/gomod /opt/citools-cache/gopath \
|
||
/opt/citools-cache/uv /opt/citools-cache/npm 2>/dev/null || true
|
||
export PATH="/opt/citools/go/bin:/opt/citools/bin:$PATH"
|
||
miss=""
|
||
for t in make python3 node npm go gofmt git curl tar sha256sum; do
|
||
command -v "$t" >/dev/null 2>&1 || miss="$miss $t"
|
||
done
|
||
if [ -n "$miss" ]; then
|
||
echo "❌ 作业镜像里缺工具:$miss"
|
||
echo " 处理:补镜像/挂载,或把该检查从清单里删掉并写明理由 —— 不允许运行时静默跳过。"
|
||
exit 1
|
||
fi
|
||
echo "工具链: $(go version) | node $(node -v) | npm $(npm -v) | $(python3 -V)"
|
||
|
||
- name: 断言门禁入口存在(缺了就是红,不许静默跳过)
|
||
run: |
|
||
set -uo pipefail
|
||
if [ ! -f scripts/ci_checks.py ]; then
|
||
echo "❌ 找不到权威门禁入口 scripts/ci_checks.py"
|
||
echo " 门禁入口是本仓唯一的检查实现;缺了就不能跑 —— 不接受'没有入口所以跳过'。"
|
||
exit 1
|
||
fi
|
||
make -n verify | head -3
|
||
echo "✅ 门禁入口就位:make verify"
|
||
|
||
- name: 权威门禁(make verify = scripts/ci_checks.py)
|
||
run: |
|
||
set +e; set +o pipefail
|
||
make verify 2>&1 | tee "${GITHUB_WORKSPACE}/ci-gate.log"
|
||
rc=${PIPESTATUS[0]} # pipefail 已关,用 PIPESTATUS 取真退出码
|
||
echo "门禁退出码=$rc"
|
||
exit "$rc"
|
||
|
||
- name: 静态预览(**先核对完整报告**;核对不过一个 publish 请求都不发)
|
||
if: always()
|
||
env:
|
||
BRIDGE_URL: ${{ vars.CI_BRIDGE_URL }}
|
||
BRIDGE_TOKEN: ${{ secrets.CI_BRIDGE_TOKEN }}
|
||
CI_REPORT: ${{ github.workspace }}/ci-report.json
|
||
run: |
|
||
set +e; set +o pipefail
|
||
: > "${GITHUB_WORKSPACE}/.ci-preview-url"
|
||
# 顺序钉死在这里:ci_preview.py 内部**先**调 ci_report.verify()(与上报步骤
|
||
# 同一份核对逻辑:清单齐不齐 / 重复 / 未知项 / 完整 SHA / 档案 / 运行模式),
|
||
# 有任何问题就一个 publish 请求都不发。
|
||
# 旧写法只读 overall_status + SHA,缺项报告会**先发出去**、等到通知步骤才发现 ——
|
||
# 预览发出去收不回来(URL 会被打开、会被转群),那时再红也撤不回来。
|
||
python3 .gitea/scripts/ci_preview.py
|
||
rc=$?
|
||
[ "$rc" = "0" ] || echo "❌ 预览步骤非 0($rc):报告核对不过或读不到报告"
|
||
exit "$rc"
|
||
|
||
- name: 上报 ci-bridge(核对报告 + 通知失败不改变质量结果)
|
||
if: always()
|
||
env:
|
||
BRIDGE_URL: ${{ vars.CI_BRIDGE_URL }}
|
||
BRIDGE_TOKEN: ${{ secrets.CI_BRIDGE_TOKEN }}
|
||
# 通知里的链接基址(**非秘密**,直接写在 workflow 里便于审查与复盘):
|
||
# CI 运行 / 提交 / 变更 三类链接统一用它拼;提交与变更里的仓库、SHA、run_id 仍是真实值。
|
||
# **不覆盖 GITHUB_SERVER_URL**(检出与所有内部调用继续用它,检出地址一个字节没变)。
|
||
# 删掉这一行 ⇒ ci_report.py 明确回退到 GITHUB_SERVER_URL,并在上报里写 link_base_source。
|
||
CI_PUBLIC_BASE_URL: https://gitea.zheromiku.top
|
||
run: |
|
||
set +e; set +o pipefail
|
||
B="${BRIDGE_URL:-}"
|
||
for cand in "$B" "http://172.17.0.1:8899" "http://192.168.50.134:8899" "http://100.92.49.78:8899"; do
|
||
if [ -n "$cand" ]; then curl -s -m 5 -o /dev/null "$cand/healthz" && { B="$cand"; break; }; fi
|
||
done
|
||
echo "使用 ci-bridge: ${B:-(不可达)}"
|
||
# 这一步**先独立核对报告**(清单齐不齐 / 完整 SHA / 档案 / 自称是否与重算一致),
|
||
# 再上报;核对不过 ⇒ 非 0(质量无从判定,绝不能报绿)。
|
||
CI_BRIDGE_RESOLVED="$B" python3 .gitea/scripts/ci_report.py
|
||
rc=$?
|
||
if [ "$rc" -ne 0 ]; then
|
||
echo "❌ 上报脚本非 0 退出:报告核对不过或读不到 —— 这次运行的质量无从判定"
|
||
fi
|
||
exit "$rc" |