feat(templates): 同步通知链接基址(CI_PUBLIC_BASE_URL)与可信来源规则
templates/ci_report.py 同步项目版(链接基址/提交链接/回退来源 + 通知字段);templates/gitea-build.yml 上报步骤加非秘密 CI_PUBLIC_BASE_URL;templates/README.md 加'上报脚本的非秘密配置'一节;01 正本 §4.7 增链接基址与'禁止全局 URL 替换';02 Runbook 增排障条目;04 加 T-9(通知链接指向内网地址)。
This commit is contained in:
@@ -272,6 +272,12 @@ Gitea 没有 `workflow: rules`,触发写在 `on:`。本环境只允许三类
|
||||
- 上报步骤用 `if: always()`(失败的构建也要报),通知失败**不能**让作业失败。
|
||||
- ⚠️ **兜底地址不要只写一个**:作业里按 `CI_BRIDGE_URL` → `http://172.17.0.1:8899` → `http://192.168.50.134:8899` → `http://100.92.49.78:8899` 顺序探测 `/healthz`,取第一个可达(现有模板已这么做,保留)。
|
||||
- ⚠️ 报告里给出的每个 URL(报告页、预览页)**都要探测到 200** 才算发出去了,否则会收到一条没有预览链接的通知。
|
||||
- ⚠️ **通知里的链接基址**用非秘密配置 `CI_PUBLIC_BASE_URL`(由 workflow 传给上报脚本):CI 运行 /
|
||||
提交 / 变更 三类链接**统一**用它拼,链接里的仓库、SHA、run_id 仍是真实值。**不覆盖
|
||||
`GITHUB_SERVER_URL`**(检出与所有内部调用继续用它)。配置缺失或非法 ⇒ 上报脚本**明确回退**
|
||||
`GITHUB_SERVER_URL`,并把基址来源写进 `link_base_source`(便于事后核对)。
|
||||
bridge 侧只渲染**允许来源**(`ci_base_url`,支持多个)的链接;**禁止**对消息文本做全局 URL 替换 ——
|
||||
提交标题、日志摘要里的 URL 是事实,一个字符都不许改。
|
||||
- ⚠️ 静态产物打包**不要产生 `.` 根成员**:`tar -czf x.tgz -C dir .` 会写入 `.` 和 `./index.html`;严格解包器可能拒绝或报错,而流水线**仍显示成功** —— 通知到了、预览没有。bridge 侧应**规范化**(剥掉 `./`,丢弃归一化为 `.`/空的成员),而不是直接拒绝整个包。
|
||||
|
||||
### 4.8 必需检查清单与结果口径(汇总不许自欺)
|
||||
|
||||
@@ -248,6 +248,7 @@ ls /srv/aiball/ci/reports/pending/ /srv/aiball/ci/reports/undelivered/
|
||||
tail -n 50 /srv/aiball/ci/logs/bridge.log
|
||||
```
|
||||
|
||||
- 通知里的链接基址:看上报里的 `link_base_source`(`CI_PUBLIC_BASE_URL` 还是回退到 `GITHUB_SERVER_URL`);bridge 只渲染允许来源的链接,非允许来源会被丢掉(消息里就没有该链接)。
|
||||
- 通知发不出去时:先看 `undelivered/`。它是**入队即落盘**的设计,进程重启不丢;`pending/` 是重试中。
|
||||
- ⚠️ 微信侧只能在"用户最近发过消息"的**会话窗口内**主动推送;窗口外发送会 `ret=-2 prepare failed`(**伪限流**,不是本机故障)。恢复办法是**本人给 bot 发一句**;队列会由 20 分钟一次的补投自己清空。**不要**为此重启任何服务。
|
||||
- ⚠️ 通知正文要**空行分隔**:单换行会被客户端的 Markdown 折叠成一段。
|
||||
@@ -266,6 +267,7 @@ tail -n 50 /srv/aiball/ci/logs/bridge.log
|
||||
| 明明匹配却报失败 | `pipefail` + `grep -q` 的 SIGPIPE | 去掉 `-q` |
|
||||
| 合并报错但 PR 其实合并了 | Gitea 成功返回空 body | **不要重试**,`GET /pulls/{n}` 读回 |
|
||||
| 通知到了但预览 404 | 产物包的 `.` 根成员被拒绝 | 规范化成员名(§4.7) |
|
||||
| 通知里的链接指向内网地址/打不开 | 没配 `CI_PUBLIC_BASE_URL`(或配成了非法值 ⇒ 已明确回退),或 bridge 的可信来源没登记公网域名 | 在 workflow 的上报步骤配 `CI_PUBLIC_BASE_URL`;bridge `ci_base_url` 里把公网域名登记上(可多个);核对上报里的 `link_base_source` |
|
||||
| 构建随机报原生模块加载失败 | 缓存里混了平台相关依赖 | 不缓存 `node_modules` |
|
||||
| "登录成功却又要重新登录" | 两个环境共用「父域 + Cookie 名」 | 改其中一个 DOMAIN 或换 COOKIE_NAME |
|
||||
| 境外请求全 000 | mihomo 节点被钉死 | `PUT /proxies/三毛机场 {"name":"<活节点>"}` |
|
||||
|
||||
@@ -151,6 +151,7 @@
|
||||
| T-6 | **协议被拼成 http** | 检出抽成 `templates/ci_checkout.sh`:remote 用 `GITHUB_SERVER_URL` 原样拼 + 断言不降级 + URL 不带凭据 + 顺带取事件 `before` | `ci_checkout.sh --print-remote`:https/http 保留、`ftp://` 拒绝;https 真跑一次检出验证 |
|
||||
| T-7 | **合并必需 context 可为空** | `adopt-existing.py`:必需 context 只认显式策略(`--require-context` / `.gitea/required-contexts.txt`),空/缺失 ⇒ 拒绝;并核对 base/state/head 仓库 | `--self-test-chain`(假 Gitea + 真实 `merge_pr()`,13 分支) |
|
||||
| T-8 | **提交范围可能漏查** | `.gitea/scripts/ci_gate.py` 以 Git 范围为准(`before..after` / 新分支 `after --not --remotes`);声明与范围不一致、空范围、浅克隆缺 before、列表提交不在检出 ⇒ 拒绝;手工运行只核 HEAD 并写明 | `--self-test` 11 分支(含"声明 6 条 / 列表 1 条 / Git 2 条 ⇒ 报错") |
|
||||
| T-9 | **通知里的链接指向内网地址**(群里点不开/换网就失效) | 上报脚本新增链接基址 `CI_PUBLIC_BASE_URL`(非秘密,workflow 传给上报脚本):run/提交/变更链接统一用它拼;缺失/非法**明确回退** `GITHUB_SERVER_URL` 并写 `link_base_source`;bridge 可信来源(`ci_base_url`,支持多个)同步登记;**不做消息文本的全局 URL 替换** | 项目侧真实链路用例:配了公网域名 ⇒ 三类链接都用它且保留真实 repo/SHA/run_id、链接目标只有可信来源;未配/非法(`ftp://`、带 userinfo、带空白)⇒ 回退且消息里不出现非法域名;提交标题里的 URL 仍是文本;真实对象的链接可达(匿名与带 token 各测) |
|
||||
|
||||
**生成器同步**:`new-project.sh` 现在会落 `ci_preview.py`、`ci_checkout.sh`、`check_secrets.py`、
|
||||
`test-scope.json`(合法但空的骨架)、`.gitea/required-contexts.txt`(**故意留空**并要求你填 —— 不预填猜出来的 context,
|
||||
|
||||
@@ -39,6 +39,17 @@ curl -m 5 -o /dev/null -w '%{http_code}\n' http://192.168.50.134:8899/healthz
|
||||
curl -m 5 -o /dev/null -w '%{http_code}\n' http://100.92.49.78:8899/healthz
|
||||
```
|
||||
|
||||
## 上报脚本的非秘密配置(写在 workflow 里,便于审查)
|
||||
|
||||
| 变量 | 作用 | 缺失时 |
|
||||
|---|---|---|
|
||||
| `CI_PUBLIC_BASE_URL` | 通知里 **CI 运行 / 提交 / 变更** 三类链接的基址(对外域名,如 `https://gitea.example.com`) | 上报脚本**明确回退** `GITHUB_SERVER_URL`(内网地址),并把来源写进上报的 `link_base_source` |
|
||||
|
||||
- **不覆盖 `GITHUB_SERVER_URL`**:检出、gate、内部调用继续用它;只影响通知里的三类链接。
|
||||
- 非法值(非 http(s)、带 userinfo、带空白)⇒ 明确回退并在 CI 日志里写明原因,**不生成垃圾链接**。
|
||||
- bridge 侧 `ci_base_url` 是链接的**可信来源**(字符串或数组):
|
||||
公网域名与(回退时的)内网地址都应登记,否则会被当成"非可信链接"丢掉。
|
||||
|
||||
## `AGENTS.md` 骨架(复制到仓库根,按本工程改写 TODO)
|
||||
|
||||
````markdown
|
||||
|
||||
+202
-6
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
"""核对门禁报告并上报 ci-bridge(bridge 负责落报告页 + 推微信)。
|
||||
"""核对门禁报告并上报 ci-bridge(bridge 负责落报告页 + 走当前通知出口:微信/钉钉)。
|
||||
|
||||
**不只看 `overall_status` 一个字段** —— 那是"信一句没有任何依据的结论"。上报前先独立核对:
|
||||
|
||||
@@ -182,6 +182,37 @@ def self_test() -> int:
|
||||
_, got, _ = verify(rep, expect, in_ci)
|
||||
if len(got) != want:
|
||||
problems.append(f"{name}:期望 {want} 个问题,实际 {len(got)} 个 {got}")
|
||||
# 通知链接基址:优先 CI_PUBLIC_BASE_URL,缺/非法都必须**明确回退**(不猜、不静默)
|
||||
env_backup = {k: os.environ.get(k) for k in ("CI_PUBLIC_BASE_URL", "GITHUB_SERVER_URL")}
|
||||
try:
|
||||
os.environ["GITHUB_SERVER_URL"] = "http://internal:3000"
|
||||
os.environ.pop("CI_PUBLIC_BASE_URL", None)
|
||||
b, src = _link_base()
|
||||
if b != "http://internal:3000" or "GITHUB_SERVER_URL" not in src:
|
||||
problems.append(f"没配 CI_PUBLIC_BASE_URL 时没回退到 GITHUB_SERVER_URL:{b!r}/{src!r}")
|
||||
os.environ["CI_PUBLIC_BASE_URL"] = "https://gitea.zheromiku.top/"
|
||||
b2, src2 = _link_base()
|
||||
if b2 != "https://gitea.zheromiku.top" or src2 != "CI_PUBLIC_BASE_URL":
|
||||
problems.append(f"配了 CI_PUBLIC_BASE_URL 没用它(或没去掉尾斜杠):{b2!r}/{src2!r}")
|
||||
for bad in ("gitea.zheromiku.top", "ftp://gitea.zheromiku.top",
|
||||
"https://u:p@gitea.zheromiku.top", "https://a b/"):
|
||||
os.environ["CI_PUBLIC_BASE_URL"] = bad
|
||||
b3, src3 = _link_base()
|
||||
if b3 != "http://internal:3000" or "回退" not in src3:
|
||||
problems.append(f"非法 CI_PUBLIC_BASE_URL({bad!r}) 没明确回退:{b3!r}/{src3!r}")
|
||||
os.environ["CI_PUBLIC_BASE_URL"] = "https://gitea.zheromiku.top"
|
||||
if _commit_url("a/b", "c" * 40, "https://gitea.zheromiku.top") != \
|
||||
f"https://gitea.zheromiku.top/a/b/commit/{'c' * 40}":
|
||||
problems.append("提交链接拼错")
|
||||
for bad_repo, bad_sha in (("onlyone", "c" * 40), ("a/b", "c" * 12), ("a/b", "")):
|
||||
if _commit_url(bad_repo, bad_sha, "https://gitea.zheromiku.top"):
|
||||
problems.append(f"仓库/短 SHA 不合法却仍拼出链接:{bad_repo!r}/{bad_sha[:12]!r}")
|
||||
finally:
|
||||
for k2, v2 in env_backup.items():
|
||||
if v2 is None:
|
||||
os.environ.pop(k2, None)
|
||||
else:
|
||||
os.environ[k2] = v2
|
||||
if problems:
|
||||
print("❌ 上报核对自检失败:", file=sys.stderr)
|
||||
for p in problems:
|
||||
@@ -192,6 +223,138 @@ def self_test() -> int:
|
||||
return 0
|
||||
|
||||
|
||||
# ── 通知消息规范(ci-notification-message-standard §5)需要的字段 ──────────────
|
||||
# 全部**由程序从真实事实算**:job 名来自 workflow 文件、范围来自门禁的同一个解析器、
|
||||
# 统计来自权威清单。缺就缺(消息里显示"未取得"),绝不用写死的值凑格式。
|
||||
def _job_name() -> str:
|
||||
"""真实 job 名:从 workflow 文件里读 `jobs:` 下的第一个 job(不写死 build)。"""
|
||||
for p in (ROOT / ".gitea" / "workflows").glob("*.y*ml"):
|
||||
try:
|
||||
txt = p.read_text(encoding="utf-8")
|
||||
except Exception:
|
||||
continue
|
||||
m = re.search(r"^jobs:\s*$\n(.*)", txt, re.M | re.S)
|
||||
if m:
|
||||
m2 = re.search(r"^ ([A-Za-z0-9_.-]+):", m.group(1), re.M)
|
||||
if m2:
|
||||
return m2.group(1)
|
||||
return os.environ.get("GITHUB_JOB", "")
|
||||
|
||||
|
||||
def _scope_for_notify():
|
||||
"""本次**准确范围**:复用门禁的同一个解析器(不另写一套范围逻辑)。
|
||||
|
||||
返回 (commits:[{sha,subject}], source:str, err:str)。取不到就给空 —— 不猜。
|
||||
"""
|
||||
try:
|
||||
sys.path.insert(0, str(ROOT / ".gitea" / "scripts"))
|
||||
import ci_gate # type: ignore
|
||||
commits, source, err = ci_gate.resolve_scope()
|
||||
out = []
|
||||
for c in (commits or [])[:10]:
|
||||
msg = str(c.get("message") or "").strip().splitlines()
|
||||
out.append({"sha": str(c.get("sha") or ""), "subject": (msg[0] if msg else "")})
|
||||
return out, source, err or ""
|
||||
except Exception as e:
|
||||
return [], "", f"{e.__class__.__name__}"
|
||||
|
||||
|
||||
def _link_base() -> tuple:
|
||||
"""通知里的链接用哪个基址:**CI_PUBLIC_BASE_URL 优先**,没配/非法就退回 GITHUB_SERVER_URL。
|
||||
|
||||
只接受 `http(s)://host[:port][/path]` 的绝对地址、不接受带凭据(userinfo)的写法。
|
||||
返回 (base, source);source 会写进上报与 CI 日志,方便事后核对"这条链接是谁给的基址"。
|
||||
|
||||
⚠️ 这是**唯一的**链接基址来源:不做"把消息文本里的 URL 全部替换一遍"那种事 ——
|
||||
那样会把提交标题、日志摘要里的 URL 也改掉(那些是事实,不许动)。
|
||||
"""
|
||||
pub = (os.environ.get("CI_PUBLIC_BASE_URL") or "").strip()
|
||||
fallback = (os.environ.get("GITHUB_SERVER_URL") or "").strip().rstrip("/")
|
||||
if pub:
|
||||
bad = None
|
||||
if not re.match(r"^https?://", pub):
|
||||
bad = "不是 http(s) 绝对地址"
|
||||
elif " " in pub or " " in pub:
|
||||
bad = "含空白字符"
|
||||
else:
|
||||
m = re.match(r"^[a-z]+://([^/]+)", pub, re.I)
|
||||
if not m or "@" in m.group(1):
|
||||
bad = "主机部分不合法或带凭据(userinfo)"
|
||||
if bad:
|
||||
return fallback, f"CI_PUBLIC_BASE_URL 非法({bad})⇒ 回退 GITHUB_SERVER_URL"
|
||||
return pub.rstrip("/"), "CI_PUBLIC_BASE_URL"
|
||||
return fallback, "GITHUB_SERVER_URL(未配 CI_PUBLIC_BASE_URL)"
|
||||
|
||||
|
||||
def _repo_ok(repo: str) -> bool:
|
||||
"""链接里的仓库必须是真的 owner/repo(两段、都非空),否则宁可不给链接。"""
|
||||
parts = [x for x in str(repo or "").split("/")]
|
||||
return len(parts) == 2 and all(x.strip() for x in parts)
|
||||
|
||||
|
||||
def _commit_url(repo: str, sha: str, base: str) -> str:
|
||||
if not _repo_ok(repo) or not base or not re.match(r"^[0-9a-f]{40}$", str(sha or "")):
|
||||
return ""
|
||||
return f"{base}/{repo}/commit/{sha}"
|
||||
|
||||
|
||||
def _compare_url(repo: str, base: str) -> str:
|
||||
"""变更列表链接:只有拿到真实的 before/after 才给(不然就是编一个打不开的地址)。"""
|
||||
ev = (os.environ.get("GITHUB_EVENT_PATH") or "").strip()
|
||||
if not base or not repo or not ev or not os.path.exists(ev):
|
||||
return ""
|
||||
try:
|
||||
with open(ev, encoding="utf-8") as fh:
|
||||
d = json.load(fh)
|
||||
before, after = str(d.get("before") or ""), str(d.get("after") or "")
|
||||
if (len(before) == 40 and len(after) == 40 and set(before) != {"0"}
|
||||
and set(after) != {"0"} and before != after):
|
||||
return f"{base}/{repo}/compare/{before}...{after}"
|
||||
except Exception:
|
||||
pass
|
||||
return ""
|
||||
|
||||
|
||||
def _first_failure(checks: list) -> dict:
|
||||
"""首个失败项(优先真失败,其次"未运行/缺项"—— 两者都不等于通过)。"""
|
||||
for c in checks or []:
|
||||
if str(c.get("status")) == "failure":
|
||||
return {"id": c.get("id"), "reason": str(c.get("reason") or "").strip()[:400]}
|
||||
for c in checks or []:
|
||||
if str(c.get("status")) in ("not_run", "unknown", "skipped"):
|
||||
return {"id": c.get("id"),
|
||||
"reason": f"未运行/跳过({c.get('status')})—— 不算通过"}
|
||||
return {}
|
||||
|
||||
|
||||
def _next_step(checks: list) -> str:
|
||||
"""可复现入口:指向项目唯一门禁入口与该检查项(真实命令,不是建议口号)。"""
|
||||
ff = _first_failure(checks)
|
||||
if ff.get("id"):
|
||||
return f"本地复现:make verify(或 python3 scripts/ci_checks.py --only {ff['id']})"
|
||||
return "本地复现:make verify(唯一门禁入口)后查看报告"
|
||||
|
||||
|
||||
def _limitations(checks: list, rep: dict) -> list:
|
||||
"""范围限制:**只写事实**(跳过了哪些检查、格式债基线还剩多少),不写"全部通过"。"""
|
||||
out = []
|
||||
skipped = [str(c.get("id")) for c in (checks or []) if str(c.get("status")) == "skipped"]
|
||||
if skipped:
|
||||
out.append("已跳过的检查(不算通过):" + "、".join(skipped[:6]))
|
||||
try:
|
||||
base = json.loads((ROOT / "web" / ".prettier-baseline.json").read_text(encoding="utf-8"))
|
||||
n = len(base.get("files") or base if isinstance(base, (list, dict)) else [])
|
||||
if isinstance(base, dict):
|
||||
n = len(base.get("files") or base.get("entries") or {})
|
||||
if n:
|
||||
out.append(f"前端格式债基线:{n} 个文件(只减不增,到期前必须清零)")
|
||||
except Exception:
|
||||
pass
|
||||
for x in (rep.get("limitations") or [])[:3]:
|
||||
out.append(str(x)[:160])
|
||||
return out[:4]
|
||||
|
||||
|
||||
def main(argv: list[str]) -> int:
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--report", default=os.environ.get("CI_REPORT", str(ROOT / "ci-report.json")))
|
||||
@@ -257,6 +420,23 @@ def main(argv: list[str]) -> int:
|
||||
if problems:
|
||||
print("❌ 汇总不可信/不完整 ⇒ 不能报绿(作业这次不算通过)", file=sys.stderr)
|
||||
|
||||
_lb_base, _lb_source = _link_base()
|
||||
repo_full = (rep.get("repository") or os.environ.get("GITHUB_REPOSITORY") or "").strip()
|
||||
if _lb_source != "CI_PUBLIC_BASE_URL":
|
||||
print(f"提示:通知链接基址 = {_lb_base or '(未取得)'}({_lb_source})", flush=True)
|
||||
else:
|
||||
print(f"通知链接基址 = {_lb_base}(来自 CI_PUBLIC_BASE_URL)", flush=True)
|
||||
if not _repo_ok(repo_full):
|
||||
print(f"⚠️ 仓库名不是 owner/repo({repo_full[:40]!r})⇒ 通知里不给 run/提交/变更 链接",
|
||||
file=sys.stderr)
|
||||
_scope_commits, _scope_source, _scope_err = _scope_for_notify()
|
||||
_scope_payload = {"commits": _scope_commits, "source": _scope_source,
|
||||
"count": len(_scope_commits)}
|
||||
if _scope_err:
|
||||
print(f"⚠️ 通知里不含本次范围摘要(范围解析:{_scope_err[:120]})", file=sys.stderr)
|
||||
payload_count = rd(os.environ.get("CI_COMMITS_COUNT_FILE", ""), "") or git(
|
||||
"rev-list", "--count", "HEAD~1..HEAD")
|
||||
|
||||
payload = {
|
||||
# —— bridge 既有字段(保持兼容,不改 bridge)——
|
||||
"repo": rep.get("repository") or os.environ.get("GITHUB_REPOSITORY", "unknown/unknown"),
|
||||
@@ -266,8 +446,7 @@ def main(argv: list[str]) -> int:
|
||||
"workflow": os.environ.get("GITHUB_WORKFLOW", "build"),
|
||||
"status": BRIDGE_STATUS.get(overall, "failure"),
|
||||
"commit_subject": git("log", "-1", "--format=%s"),
|
||||
"commit_count": rd(os.environ.get("CI_COMMITS_COUNT_FILE", ""), "") or git("rev-list",
|
||||
"--count", "HEAD~1..HEAD"),
|
||||
"commit_count": payload_count,
|
||||
"duration_s": rep.get("duration_s", "?"),
|
||||
"preview_url": rd(str(ROOT / ".ci-preview-url")),
|
||||
"log": rd(os.environ.get("CI_GATE_LOG", ""))[-3500:],
|
||||
@@ -283,9 +462,8 @@ def main(argv: list[str]) -> int:
|
||||
"event": rep.get("event"),
|
||||
"run_mode": rep.get("run_mode"),
|
||||
"run_id": os.environ.get("GITHUB_RUN_ID", ""),
|
||||
"run_url": (f"{os.environ.get('GITHUB_SERVER_URL', '')}/"
|
||||
f"{rep.get('repository', '')}/actions/runs/{os.environ.get('GITHUB_RUN_ID', '')}"
|
||||
if os.environ.get("GITHUB_RUN_ID") else ""),
|
||||
"run_url": (f"{_lb_base}/{repo_full}/actions/runs/{os.environ.get('GITHUB_RUN_ID', '')}"
|
||||
if (os.environ.get("GITHUB_RUN_ID") and _lb_base and _repo_ok(repo_full)) else ""),
|
||||
"checks": checks,
|
||||
"manifest": rep.get("manifest"),
|
||||
"problems": (rep.get("problems") or []) + problems,
|
||||
@@ -293,6 +471,24 @@ def main(argv: list[str]) -> int:
|
||||
"expected_sha": expect_sha, "reported_sha": rep.get("commit_sha")},
|
||||
"notification_status": "not_attempted",
|
||||
"deployed": False, # 本 workflow 只做 CI:没有部署,报告里也这么写
|
||||
|
||||
# —— 通知消息规范需要的字段(缺就缺,不许编)——
|
||||
"schema_version": 2,
|
||||
"job": _job_name(), # 真实 job 名(来自 workflow 文件)
|
||||
"run_number": os.environ.get("GITHUB_RUN_NUMBER", ""),
|
||||
"run_attempt": os.environ.get("GITHUB_RUN_ATTEMPT", "1"),
|
||||
"commits": _scope_payload["commits"], # 准确范围里的提交标题(≤10 条)
|
||||
"range_source": _scope_payload["source"], # 范围是怎么算出来的(可复盘)
|
||||
"commit_count": (_scope_payload["count"] or payload_count), # 条数由 Git 算
|
||||
"compare_url": _compare_url(repo_full, _lb_base),
|
||||
"commit_url": _commit_url(repo_full, rep.get("commit_sha") or "", _lb_base),
|
||||
"link_base": _lb_base, # 链接基址(可复盘)
|
||||
"link_base_source": _lb_source, # 基址从哪来:CI_PUBLIC_BASE_URL / GITHUB_SERVER_URL 回退
|
||||
"run_duration_s": "", # 整次运行耗时:本 workflow 没留起点 ⇒ 不编
|
||||
"duration_kind": "gate",
|
||||
"first_failure": _first_failure(checks),
|
||||
"next_step": _next_step(checks),
|
||||
"limitations": _limitations(checks, rep),
|
||||
}
|
||||
|
||||
base = (os.environ.get("CI_BRIDGE_RESOLVED") or os.environ.get("BRIDGE_URL") or "").rstrip("/")
|
||||
|
||||
@@ -225,6 +225,11 @@ jobs:
|
||||
env:
|
||||
BRIDGE_URL: ${{ vars.CI_BRIDGE_URL }}
|
||||
BRIDGE_TOKEN: ${{ secrets.CI_BRIDGE_TOKEN }}
|
||||
# 通知里的链接基址(**非秘密**,写在 workflow 里便于审查):CI 运行 / 提交 / 变更
|
||||
# 三类链接统一用它拼。**不覆盖 GITHUB_SERVER_URL**(检出与内部调用继续用它)。
|
||||
# 删掉这一行 ⇒ ci_report.py 明确回退 GITHUB_SERVER_URL,并在上报里写 link_base_source。
|
||||
# 换成你的实例对外域名。
|
||||
CI_PUBLIC_BASE_URL: https://gitea.example.com
|
||||
run: |
|
||||
set +e; set +o pipefail
|
||||
B="${BRIDGE_URL:-}"
|
||||
|
||||
Reference in New Issue
Block a user