Files
HomeAgent/deploy/scripts/upload_assets.py
JianFeeeee ffe9bc2cff refactor(ci,docs): gitcode 镜像仓改名 HomeAgent→HologramAgent,定义点取值对齐
gitcode 侧镜像仓已完成改名,旧名 HomeAgent 变成 301 跳转,真仓是
HologramAgent。dbb787a 里三处 GC_REPO 定义点仍取旧名(命中 301),本批只
改取值与说明,**不改变 dbb787a 已建立的「每个文件恰一个定义点」结构**,
不引入任何新字面量。

实测(2026-10-21 20:1x,直连不带代理,非推测):
  $ curl -s -o /dev/null -w '%{http_code}' https://gitcode.com/JianFeeeee/<repo>

  HologramAgent     → 200  ← 当前取值
  HomeAgent         → 301  ← 旧名,跳转到 HologramAgent
  homeagent-sdk     → 200(SDK 仓,另一对象)
  hologramagentsdk  → 200(SDK 仓,另一对象)
  homeagentsdk      → 404
  HologramAgent-sdk → 404

「301 不是另一个仓,就是同一仓改名」的判定依据:两个地址 `git ls-remote`
得到的 refs 列表 md5 相同(同一组分支与 SHA),且 curl 页面 title 一致。

三个定义点(各文件恰好 1 处,消费点全部走变量,本次只改这三行的取值):
1. .github/workflows/ci.yml   job sync-gitcode-main 的 env.GC_REPO
2. .github/workflows/release.yml job sync-gitcode   的 env.GC_REPO
3. deploy/scripts/upload_assets.py 的 os.environ.get("GITCODE_REPO", ...) 默认值
消费点未动:ci.yml 2 处、release.yml 4 处命令行,仍全部是 ${GC_REPO}。

注释同步改准(原来把 301 的仓写成「200 ← 当前用这个」,是错的):
- 两个 workflow 的取值依据清单换成上面六个仓的现状,注明实测时间与 curl 命令
- 明确 HomeAgent = 301 跳转、HologramAgent = 当前取值
- 「以后再改名只改这三行」的说明保留

docs/zh/ci-cd-runbook.md 同步:
- 总览表 gitcode 镜像改为 HologramAgent(与主仓同名)
- 「当前取值」段与六仓实测表、301 判定依据
- API 侧口径也改准:/api/v5/repos/JianFeeeee/{HomeAgent,HologramAgent}
  不带 token 均 400(需鉴权,非仓不存在);/releases/tags/v1.3.13
  两名字均 200 且响应体一致。旧文档写「HologramAgent→404 是死链」已不成立。
- §3.3 curl 验活地址、§3.4 手工上传示例的 GITCODE_REPO 同步为新值

判据(正向 14 项 + 变异 11 条,全部实跑):
- 两个 workflow yaml.safe_load 解析通过;每个 env.GC_REPO 恰好 1 处
- 三处定义点取值完全一致(P4);消费点计数 ci=2/release=4(P6)
- P8 改为「不带 -L 的 200」:这正是本次误判的根因——之前用 curl -L 测,
  301 跟随后也是 200,把「旧名跳转」误读成「旧名是真仓、新名 404」
- 变异 M11「三处一致改回旧名 HomeAgent」只被 P8 抓住(P4 仍绿),
  证明该判据能单独区分「一致但指向 301 旧名」
- 所有 step run 块过 bash -n(30 个 run 块,0 错)
- gitcode 作 import host 残留 0;工作区无 .go/.lua/go.mod/go.sum 变更

零代码改动:packaging_test.go 里「禁止历史模块路径」的反向断言未动,
third_party 未动,systemd 相关行未动。
2026-10-21 20:15:41 +08:00

146 lines
6.0 KiB
Python
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""上传 release 资产到 gitcode(两步:取签名 URL → PUT 到 OBS)。
用法: upload_assets.py <tag> <token> [file...]
不传 file 时上传 dist/release/ 下全部发布产物。
环境变量:
GITCODE_REPO 目标仓库,默认 JianFeeeee/HologramAgent(SDK 仓传 JianFeeeee/homeagent-sdk)
这是 gitcode 镜像仓名的**唯一定义处**(workflow 里通过 job 级
env.GC_REPO 传入同一个值)。改 gitcode 仓名时改这里 + 两个
workflow 的 GC_REPO,共三处(每个文件各一个定义点)。
取值依据(实测 2026-10-21 20:1x,直连不带代理):
JianFeeeee/HologramAgent → 200 ← 当前取值
JianFeeeee/HomeAgent → 301 ← 旧名,跳转到上面
JianFeeeee/homeagent-sdk → 200(SDK 仓,另一对象)
JianFeeeee/hologramagentsdk → 200(SDK 仓,另一对象)
JianFeeeee/homeagentsdk → 404
JianFeeeee/HologramAgent-sdk → 404
gitcode 侧镜像仓已由 HomeAgent 改名为 HologramAgent(与 GitHub
主仓同名),旧地址仍 301,别再写旧名。
ASSET_DIR 资产目录,默认 <repo>/dist/release
为何两步:gitcode 的 release 附件不走 API 直传,而是先向
`releases/<tag>/upload_url` 要一个 OBS 预签名 URL(带 x-obs-* 回调头),
再把文件 PUT 到那个 URL。回调头必须原样透传,否则 OBS 收下了文件但
gitcode 侧不会登记为 release 附件。
"""
import json
import os
import sys
import urllib.error
import urllib.parse
import urllib.request
REPO = os.environ.get("GITCODE_REPO", "JianFeeeee/HologramAgent")
API = "https://gitcode.com/api/v5/repos"
# 发布产物后缀。注意 Windows 安装器是 HomeAgent_v*_win64.exe,
# 与 bin/ 里的裸 .exe 靠 _win64.exe 后缀区分。
ARTIFACT_SUFFIXES = (
".tar.gz",
".zip",
".deb",
".rpm",
".pkg",
"_win64.exe",
# 插件包。之前不在白名单里,会被静默跳过——而 release 本该带上它们,
# 否则用户要自己装 Go + hmapdev 逐插件构建(见 SDK 仓 scripts/build_plugin_bundles.sh)。
".hmap",
# 插件包汇总校验和(与 SHA256SUMS 同性质,独立文件免得混淆内核包与插件)
"SHA256SUMS.plugins",
)
def is_artifact(name: str) -> bool:
return name == "SHA256SUMS" or name.endswith(ARTIFACT_SUFFIXES)
def get_upload_url(tag: str, token: str, filename: str) -> tuple[str, dict]:
q = urllib.parse.urlencode({"file_name": filename})
url = f"{API}/{REPO}/releases/{tag}/upload_url?{q}"
req = urllib.request.Request(url, headers={"private-token": token})
with urllib.request.urlopen(req, timeout=30) as r:
data = json.loads(r.read())
return data["url"], data.get("headers", {})
def put_file(url: str, headers: dict, path: str) -> tuple[int, str]:
size = os.path.getsize(path)
with open(path, "rb") as f:
body = f.read()
req = urllib.request.Request(url, data=body, method="PUT")
for k, v in headers.items():
req.add_header(k, v)
req.add_header("Content-Length", str(size))
try:
# 大文件(Full 变体安装包近 100MB)给足超时。
with urllib.request.urlopen(req, timeout=900) as r:
return r.status, r.read().decode("utf-8", "replace")[:300]
except urllib.error.HTTPError as e:
return e.code, e.read().decode("utf-8", "replace")[:300]
except Exception as e: # noqa: BLE001
return 0, f"{type(e).__name__}: {e}"
def project_root() -> str:
"""向上找带 go.mod 的目录作为仓库根。
为何不数 dirname:本脚本初版在 scripts/(深度 1),移到 deploy/scripts/
(深度 2)后写死的两层 dirname 就指向了 deploy/dist/release,上传直接
FileNotFoundError。这正是 v0.7.2 那次 package/ → deploy/packaging/ 打断
PROJECT_ROOT 的同一个坑,改成按标记文件定位以后怎么挑位置都不会错。
"""
d = os.path.dirname(os.path.abspath(__file__))
while d != os.path.dirname(d):
if os.path.exists(os.path.join(d, "go.mod")):
return d
d = os.path.dirname(d)
# 实在找不到(脚本被单独拷出仓库)就回退到 cwd,给 ASSET_DIR 一个机会
return os.getcwd()
def main() -> int:
if len(sys.argv) < 3:
print(__doc__)
return 2
tag, token = sys.argv[1], sys.argv[2]
outdir = os.environ.get("ASSET_DIR") or os.path.join(
project_root(), "dist", "release"
)
if not os.path.isdir(outdir):
print(f"error: 资产目录不存在: {outdir}")
print(" 用 ASSET_DIR=<目录> 显式指定,或先跑构建生成 dist/release/")
return 2
files = sys.argv[3:] or sorted(
f for f in os.listdir(outdir) if is_artifact(f)
)
if not files:
print(f"error: {outdir} 下没有可识别的发布产物")
return 2
print(f"repo={REPO} tag={tag} dir={outdir}", flush=True)
failed = []
for name in files:
path = os.path.join(outdir, name)
if not os.path.isfile(path):
print(f"skip (missing): {name}", flush=True)
continue
mib = os.path.getsize(path) / 1048576
print(f"==> {name} ({mib:.1f} MiB)", flush=True)
try:
url, headers = get_upload_url(tag, token, name)
except Exception as e: # noqa: BLE001
print(f" upload_url FAILED: {e}", flush=True)
failed.append(name)
continue
status, body = put_file(url, headers, path)
ok = 200 <= status < 300
print(f" PUT -> {status} {'OK' if ok else body}", flush=True)
if not ok:
failed.append(name)
print(f"\n{'ALL OK' if not failed else f'{len(failed)} FAILED: ' + ', '.join(failed)}")
return 1 if failed else 0
if __name__ == "__main__":
sys.exit(main())