mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-11-01 14:57:38 +00:00
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 相关行未动。
146 lines
6.0 KiB
Python
Executable File
146 lines
6.0 KiB
Python
Executable File
#!/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())
|