Files
codex-app-server/chv-guest/check_lockfile.py
T
host 4d09518338 feat(chv-guest): 轻量镜像 + Gitea 双向同步(停全量版,改 Ubuntu Minimal)
- build.sh 改轻量:Ubuntu Minimal 底座、apt 精简(git/ssh/cloud-init/
  python3/pip/curl/ca-certificates/xz-utils/fonts-wqy-microhei)、
  Playwright + chromium-headless-shell、去 B-16 +12G 扩盘;
  加 git + gt 1.0.0(官方 install.sh + SHA256 校验,GITEA_CLI_VERSION
  钉 1.0.0)与 chv-sync 启动钩子(cloud-init runcmd)
- B-19(新发现):dpkg freeze 的 ${Package} 会被客机 shell 展开
  成空,284 行全空白;加反斜杠转义,探针实测已出真实版本
- buildinfo.py:轻量 contents、chromium-headless-shell、gt_version
- generate/check_lockfile.py:gt/chv-sync 声明来源、headless-shell 映射
- chv-sync.sh(新):boot/pull/push/status,分叉/脏树失败关闭,
  凭证走 seed 盘 guest-env,不进镜像
- README §1/§2/§3/§4 改轻量口径,新增 §7 Gitea 双向同步
- tests:ChvSyncTest 6 项(本地 bare 仓当远端);针对性测试 144 项全过
2026-11-06 09:47:54 +00:00

139 lines
4.9 KiB
Python
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
"""校验已构建镜像的 BUILDINFO 供应链清单(事后 lockfile 检查)。
事前 apt/pip 精确 lockfile 无法在本轨生成:apt 版本在构建
宿主上解析、pip 版本在客机里解析,没有构建宿主就编不出
版本号——本脚本绝不编造。构建时 build.sh 已从客机读回
dpkg + pip freeze 写进 BUILDINFO.versions(事后 lockfile),
本脚本校验它:versions 非空、每条有版本、contents 全覆盖、
tag 与预期一致、可选的冻结清单(--frozen JSON,形如
{"dpkg": ["pkg version", ...]})双向比对,漂移即退出非 0。
用法:
python3 check_lockfile.py out/BUILDINFO.json [--tag TAG]
[--frozen frozen.json]
"""
import json
import sys
# 非包名的内容项:版本来自 BUILDINFO 的声明字段,不是 versions 列表。
DECLARED_SOURCES = {
"codex": "codex_version",
"codex-code-mode-host": "codex_version",
"codex-resources/bwrap": "codex_version",
"/app/host.py": "repo_commit",
"gt": "gt_version",
"chv-sync": "repo_commit",
}
def _names(entries, pip_style=False):
"""versions 条目 → 小写名集合。dpkg 行是 "name version",
pip freeze 行是 "name==version"。"""
names = set()
for entry in entries or []:
text = str(entry)
if pip_style and "==" in text:
names.add(text.split("==", 1)[0].lower())
else:
parts = text.split(None, 1)
if parts:
names.add(parts[0].lower())
return names
def _check_format(kind, entries):
"""每条须带非空版本:dpkg "name version",pip "name==version"。"""
for entry in entries or []:
text = str(entry)
if kind == "pip" and "==" in text:
if not text.split("==", 1)[1].strip():
print("版本为空的条目:%s %r" % (kind, entry),
file=sys.stderr)
return False
else:
parts = text.split()
if len(parts) < 2 or not parts[1]:
print("版本为空的条目:%s %r" % (kind, entry),
file=sys.stderr)
return False
return True
def main(argv):
if len(argv) < 2:
print("用法:check_lockfile.py BUILDINFO.json [--tag TAG] "
"[--frozen frozen.json]", file=sys.stderr)
return 2
path = argv[1]
tag = None
frozen_path = None
i = 2
while i < len(argv):
if argv[i] == "--tag" and i + 1 < len(argv):
tag = argv[i + 1]
i += 2
elif argv[i] == "--frozen" and i + 1 < len(argv):
frozen_path = argv[i + 1]
i += 2
else:
print("未知参数:%s" % argv[i], file=sys.stderr)
return 2
with open(path) as fh:
data = json.load(fh)
if tag is not None and data.get("tag") != tag:
print("tag 不一致:%s != %s" % (data.get("tag"), tag),
file=sys.stderr)
return 1
versions = data.get("versions") or {}
if not versions.get("dpkg") or not versions.get("pip"):
print("BUILDINFO.versions.dpkg/pip 为空,供应链清单缺失",
file=sys.stderr)
return 1
if not _check_format("dpkg", versions.get("dpkg")):
return 1
if not _check_format("pip", versions.get("pip")):
return 1
# contents 全覆盖:每项须在 dpkg/pip 名单里,或有声明字段版本。
covered = _names(versions.get("dpkg")) | _names(
versions.get("pip"), pip_style=True)
missing = []
for name in data.get("contents") or []:
if name.lower() in covered:
continue
source = DECLARED_SOURCES.get(name)
if source and data.get(source):
continue
if name.lower() in ("chromium", "chromium-headless-shell") \
and versions.get("chromium"):
continue
missing.append(name)
if missing:
print("contents 未覆盖:%s" % sorted(missing), file=sys.stderr)
return 1
if frozen_path:
with open(frozen_path) as fh:
frozen = json.load(fh)
for kind in ("dpkg", "pip"):
want = set(frozen.get(kind) or [])
got = set(versions.get(kind) or [])
absent = want - got
if absent:
print("%s 漂移:冻结清单里有、构建产物里无:%s"
% (kind, sorted(absent)), file=sys.stderr)
return 1
extra = got - want
if extra:
print("%s 漂移:构建产物里有、冻结清单里无:%s"
% (kind, sorted(extra)), file=sys.stderr)
return 1
print("lockfile 校验通过:tag=%s dpkg=%d pip=%d contents=%d"
% (data.get("tag"), len(versions["dpkg"]),
len(versions["pip"]),
len(data.get("contents") or [])))
return 0
if __name__ == "__main__":
raise SystemExit(main(sys.argv))