From 2d12179b9a7b162bbb6c3253aa0afb6c895f1261 Mon Sep 17 00:00:00 2001 From: host Date: Sat, 7 Nov 2026 03:56:24 +0000 Subject: [PATCH] =?UTF-8?q?fix(chv-muse-r11fix):=20authd=20=E5=B1=82=201?= =?UTF-8?q?=20=E5=8F=B6=E5=AD=90=E6=A0=91=E8=A1=A5=20errors.py=20+=20stub?= =?UTF-8?q?=20=5F=5Finit=5F=5F=E3=80=81=E5=B1=82=202=20seed=20=E8=A1=A5?= =?UTF-8?q?=E4=B8=89=E9=94=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 层 1:真实的 cloud/chv/__init__.py import executor/orphans,拷进 authd 最小叶子树(/usr/local/lib/chv-authd)会让 chv-authd 在 import 时 ModuleNotFoundError 而 crash loop——先是 errors.py 缺失,补上后又在 executor 上失败(探针 fixprobe3)。新增 chv-guest/authd_init_stub.py(仅导出 ChvFailure),build.sh 拷 errors.py + stub 作叶子树的 __init__.py,不再拷真实包 __init__; P3_LIBS_PRESENT 条件加 errors.py;EXTRA_CONTENTS 加 errors.py 与 __init__.py;两 lockfile 脚本的 DECLARED_SOURCES 同款登记。 层 2:_seed_env_entries 把 guest-env 里的 GITEA_URL/GITEA_USER/SYNC_REPO 抄进 authd-seed(build_server 要求四键齐全,只 GITEA_TOKEN 即 ValueError);没有不补、不编 默认值。测试:seed 三键落盘/缺键不补、干跑夹具含新文件、lockfile 合成清单含新两项。 --- chv-guest/authd_init_stub.py | 14 +++++++++++ chv-guest/build.sh | 19 +++++++++++--- chv-guest/check_lockfile.py | 4 +++ chv-guest/generate_lockfile.py | 5 ++++ cloud/chv/hostkeys.py | 24 +++++++++++++----- cloud/tests/test_chv_muse_p2.py | 9 +++++-- cloud/tests/test_chv_muse_p3.py | 43 ++++++++++++++++++++++++++++++++ cloud/tests/test_chv_residual.py | 5 +++- 8 files changed, 110 insertions(+), 13 deletions(-) create mode 100644 chv-guest/authd_init_stub.py diff --git a/chv-guest/authd_init_stub.py b/chv-guest/authd_init_stub.py new file mode 100644 index 0000000..0903482 --- /dev/null +++ b/chv-guest/authd_init_stub.py @@ -0,0 +1,14 @@ +"""chv-authd 叶子树的 stub cloud/chv/__init__.py(round-11 真机修复,层 1)。 + +真实的 cloud/chv/__init__.py import executor/orphans(进而要整套 +cloud 包),拷进 authd 的最小叶子树(/usr/local/lib/chv-authd) +会让 chv-authd 在 import 时 ModuleNotFoundError 而 crash loop: +先是 cloud.chv.errors 缺失,补上 errors.py 后又会在 executor +上失败。探针 fixprobe3 的形状:叶子树里只放 errors.py + 仅导出 +ChvFailure 的 stub __init__.py——authd 只需要这个名字。 +build.sh 拷本文件作叶子树的 cloud/chv/__init__.py,不拷真实的 +包 __init__。 +""" +from cloud.chv.errors import ChvFailure + +__all__ = ["ChvFailure"] diff --git a/chv-guest/build.sh b/chv-guest/build.sh index 6cc41d5..2ccf755 100755 --- a/chv-guest/build.sh +++ b/chv-guest/build.sh @@ -188,14 +188,18 @@ done #(test_chv_residual.BuildScriptMinimalAppTest 钉住),缺失时只 # 警告、不失败——缺叶子模块的镜像里 chv-authd 起不来,chv-sync # 按 P3 差距表 §3.5 回落旧落盘路径并警告,绝不静默假装有 authd。 -for p3_file in authd.py git-credential-chv-authd chv-authd.service; do +# round-11 真机修复(层 1):叶子树还须 errors.py + stub +# __init__(authd_init_stub.py)——真实的 cloud/chv/__init__.py +# import executor/orphans,拷进最小叶子树会让 chv-authd +# ModuleNotFoundError 而 crash loop(探针 fixprobe3 的形状)。 +for p3_file in authd.py git-credential-chv-authd chv-authd.service authd_init_stub.py; do if [ ! -f "$SCRIPT_DIR/$p3_file" ]; then echo "缺少 $p3_file(Muse P3 客机文件):$SCRIPT_DIR" >&2 exit 1 fi done P3_LIBS_PRESENT=0 -if [ -f "$REPO_DIR/cloud/chv/authd.py" ] && [ -f "$REPO_DIR/cloud/chv/sentinel.py" ] && [ -f "$REPO_DIR/cloud/__init__.py" ] && [ -f "$REPO_DIR/cloud/chv/__init__.py" ]; then +if [ -f "$REPO_DIR/cloud/chv/authd.py" ] && [ -f "$REPO_DIR/cloud/chv/sentinel.py" ] && [ -f "$REPO_DIR/cloud/chv/errors.py" ] && [ -f "$REPO_DIR/cloud/__init__.py" ] && [ -f "$REPO_DIR/cloud/chv/__init__.py" ]; then P3_LIBS_PRESENT=1 else echo "警告:Muse P3 云端叶子模块不在仓库里(cloud/chv/authd.py 与 sentinel.py),镜像暂不含 authd 叶子模块" >&2 @@ -319,11 +323,18 @@ VC_ARGS=(-a "$OUT/disk.raw" # 拷入镜像(本分支已有,合入后自动生效);不存在时只警告、不失败—— # M4 既定 build.sh 不硬要求 cloud/,见上。缺叶子模块时 chv-authd # 守护进程起不来,chv-sync 按差距表 §3.5 回落旧路径并警告。 +# round-11 真机修复(层 1):叶子树是 errors.py + stub __init__ +#(authd_init_stub.py),不拷真实的 cloud/chv/__init__.py(它 +# import executor/orphans,会让 authd 在最小叶子树里 +# ModuleNotFoundError 而 crash loop);cloud/__init__.py 本身只 +# 有 docstring,照常拷。 if [ "$P3_LIBS_PRESENT" = "1" ]; then VC_ARGS+=(--copy-in "$REPO_DIR/cloud/chv/authd.py:/usr/local/lib/chv-authd/cloud/chv/" --copy-in "$REPO_DIR/cloud/chv/sentinel.py:/usr/local/lib/chv-authd/cloud/chv/" + --copy-in "$REPO_DIR/cloud/chv/errors.py:/usr/local/lib/chv-authd/cloud/chv/" --copy-in "$REPO_DIR/cloud/__init__.py:/usr/local/lib/chv-authd/cloud/" - --copy-in "$REPO_DIR/cloud/chv/__init__.py:/usr/local/lib/chv-authd/cloud/chv/") + --copy-in "$SCRIPT_DIR/authd_init_stub.py:/usr/local/lib/chv-authd/cloud/chv/" + --run-command 'mv /usr/local/lib/chv-authd/cloud/chv/authd_init_stub.py /usr/local/lib/chv-authd/cloud/chv/__init__.py') fi # P1 交接(Muse P1 的 vsock_notify.py):文件在仓库里存在时拷入 # 镜像(P1 分支才有,本分支尚无);不存在时只警告、不失败—— @@ -395,7 +406,7 @@ else echo "警告:CHROMIUM_HEADLESS_SHELL_REV 未设,Chromium 版本只记录、不钉死(V-9 验收须设该变量重建)" >&2 fi EXTRA_CONTENTS="" - [ "$P3_LIBS_PRESENT" = "1" ] && EXTRA_CONTENTS="cloud/chv/authd.py,cloud/chv/sentinel.py" + [ "$P3_LIBS_PRESENT" = "1" ] && EXTRA_CONTENTS="cloud/chv/authd.py,cloud/chv/sentinel.py,cloud/chv/errors.py,cloud/chv/__init__.py" [ "$VSOCK_NOTIFY_PRESENT" = "1" ] && EXTRA_CONTENTS="${EXTRA_CONTENTS:+$EXTRA_CONTENTS,}vsock_notify" python3 "$SCRIPT_DIR/buildinfo.py" "$OUT" "$TAG" "$UBUNTU_VERSION" "$CODEX_VERSION" "$REPO_COMMIT" "$UBUNTU_IMAGE_SHA256" "${PLAYWRIGHT:-1}" "${CHROMIUM_HEADLESS_SHELL_REV:-}" "$EXTRA_CONTENTS" fi diff --git a/chv-guest/check_lockfile.py b/chv-guest/check_lockfile.py index 4d39fdb..71bd249 100644 --- a/chv-guest/check_lockfile.py +++ b/chv-guest/check_lockfile.py @@ -34,11 +34,15 @@ DECLARED_SOURCES = { # 叶子模块与 vsock 通知脚本,均是仓库内脚本,版本随 # repo_commit。缺了它们 check 会报 contents 未覆盖(717efc4 # 真机实测:6 项 unresolved,verify 与 check 双双失败)。 + # round-11 真机修复(层 1):叶子树新增 errors.py 与 stub + # __init__.py(authd_init_stub.py),同款 repo_commit 登记。 "chv-authd": "repo_commit", "git-credential-chv-authd": "repo_commit", "chv-authd.service": "repo_commit", "cloud/chv/authd.py": "repo_commit", "cloud/chv/sentinel.py": "repo_commit", + "cloud/chv/errors.py": "repo_commit", + "cloud/chv/__init__.py": "repo_commit", "vsock_notify": "repo_commit", } diff --git a/chv-guest/generate_lockfile.py b/chv-guest/generate_lockfile.py index 5eb0ee2..091bb80 100644 --- a/chv-guest/generate_lockfile.py +++ b/chv-guest/generate_lockfile.py @@ -55,11 +55,16 @@ DECLARED_SOURCES = { # 叶子模块与 vsock 通知脚本,均是仓库内脚本,版本随 # repo_commit。缺了它们 generate 会记 6 项 unresolved, # verify 与 check 双双失败(717efc4 真机实测)。 + # round-11 真机修复(层 1):叶子树新增 errors.py 与 stub + # __init__.py(authd_init_stub.py),同款 repo_commit 登记, + # 缺了它们新镜像的 lockfile 同样会 unresolved。 "chv-authd": "repo_commit", "git-credential-chv-authd": "repo_commit", "chv-authd.service": "repo_commit", "cloud/chv/authd.py": "repo_commit", "cloud/chv/sentinel.py": "repo_commit", + "cloud/chv/errors.py": "repo_commit", + "cloud/chv/__init__.py": "repo_commit", "vsock_notify": "repo_commit", } diff --git a/cloud/chv/hostkeys.py b/cloud/chv/hostkeys.py index 853a4b4..990335a 100644 --- a/cloud/chv/hostkeys.py +++ b/cloud/chv/hostkeys.py @@ -193,10 +193,16 @@ def build_seed_user_data(private_key_text: str, public_key_text: str) -> str: def _authd_env_entry(authd_env: dict) -> list: """authd-seed 的 write_files 条目行(0600 root,值经 - shlex.quote 引用):只装 AUTHD_SECRET_KEYS 命中的密钥, - 与 guest-env 条目并列写进同一 write_files 列表(绝不另写 - 一个 write_files: 键,见 _guest_env_entry 的 YAML 重复键 - 禁令)。""" + shlex.quote 引用):装 AUTHD_SECRET_KEYS 命中的密钥 + (本期只有 GITEA_TOKEN),外加与 guest-env 同源抄进来的 + 三个非密钥同步变量(GITEA_URL/GITEA_USER/SYNC_REPO—— + round-11 真机修复层 2:authd 的 build_server 要求 seed + 里同时有这三键,只 GITEA_TOKEN 会 ValueError;探针 + fixprobe3 即从 guest-env 补这三键)。三键只在调用方 + guest-env 里有才抄,没有不补、不编默认值;与 guest-env + 条目并列写进同一 write_files 列表(绝不另写一个 + write_files: 键,见 _guest_env_entry 的 YAML 重复键禁令)。 + """ lines = [ " - path: %s" % AUTHD_SEED_PATH, " permissions: '0600'", @@ -296,12 +302,18 @@ def _write_seed_meta(seed_dir: str, vm_name: str) -> str: def _seed_env_entries(merged_env: dict, authd_env: dict | None) -> list: """guest-env 与 authd-seed 两路的 write_files 条目行 - (皆空即空列表,不写条目)。""" + (皆空即空列表,不写条目)。authd-seed 除了密钥还抄 + guest-env 里的 GITEA_URL/GITEA_USER/SYNC_REPO(层 2, + 见 _authd_env_entry)——build_server 要求四键齐全。""" extra_lines = [] if merged_env: extra_lines += _guest_env_entry(merged_env) if authd_env: - extra_lines += _authd_env_entry(authd_env) + seed_env = dict(authd_env) + for key in ("GITEA_URL", "GITEA_USER", "SYNC_REPO"): + if key in merged_env: + seed_env[key] = merged_env[key] + extra_lines += _authd_env_entry(seed_env) return extra_lines diff --git a/cloud/tests/test_chv_muse_p2.py b/cloud/tests/test_chv_muse_p2.py index c510e40..4b4e505 100644 --- a/cloud/tests/test_chv_muse_p2.py +++ b/cloud/tests/test_chv_muse_p2.py @@ -575,13 +575,14 @@ class BuildDryRunTest(unittest.TestCase): "generate_lockfile.py", "chv-sync.sh", "chv-cell.py", "chv-broker.py", "chv-browser.py", "authd.py", "git-credential-chv-authd", - "chv-authd.service"): + "chv-authd.service", "authd_init_stub.py"): shutil.copy2(ROOT / "chv-guest" / name, os.path.join(guest, name)) shutil.copy2(ROOT / "host.py", os.path.join(repo, "host.py")) # round-11:build.sh 要求 P3 云端叶子模块在仓里(fail-fast), # 夹具须一并拷入,否则干跑在 P3 预检就失败。 for rel in ("cloud/chv/authd.py", "cloud/chv/sentinel.py", + "cloud/chv/errors.py", "cloud/__init__.py", "cloud/chv/__init__.py"): dest = os.path.join(repo, rel) os.makedirs(os.path.dirname(dest), exist_ok=True) @@ -623,7 +624,11 @@ class BuildDryRunTest(unittest.TestCase): "systemctl enable chv-broker chv-browser", "chv-authd", "git-credential-chv-authd", "systemctl enable chv-authd", - "systemctl start chv-authd"): + "systemctl start chv-authd", + # round-11 真机修复(层 1):叶子树含 + # errors.py + stub __init__,不拷真实的 + # cloud/chv/__init__.py。 + "errors.py", "authd_init_stub.py"): self.assertIn(needle, out, needle) # P1 文件不在本分支:干跑只警告、不失败。 self.assertIn("vsock_notify.py 不在仓库里", out) diff --git a/cloud/tests/test_chv_muse_p3.py b/cloud/tests/test_chv_muse_p3.py index 390743d..1b88993 100644 --- a/cloud/tests/test_chv_muse_p3.py +++ b/cloud/tests/test_chv_muse_p3.py @@ -367,6 +367,49 @@ class SeedSplitTest(unittest.TestCase): self.assertNotIn("GITEA_TOKEN", entries["/etc/chv/guest-env"]["content"]) + def test_write_seed_authd_entry_carries_sync_keys(self): + # round-11 真机修复(层 2):authd-seed 须同时有 + # GITEA_URL/GITEA_USER/SYNC_REPO,build_server 才会放行 + #(只 GITEA_TOKEN 时 ValueError,探针 fixprobe3 即从 + # guest-env 补这三键);三键只在 guest-env 里有才抄。 + import yaml + with tempfile.TemporaryDirectory() as tmp: + seed_dir = os.path.join(tmp, "seed") + write_seed(seed_dir, "vm1", "PRIV", "ssh-ed25519 AAAAx", + guest_env={ + "GITEA_URL": "https://gitea.tose.top", + "GITEA_USER": "admin", + "SYNC_REPO": "admin/chv-sync-test", + "SYNC_BRANCH": "main"}, + authd_env={"GITEA_TOKEN": TOKEN}) + with open(os.path.join(seed_dir, "user-data")) as fh: + parsed = yaml.safe_load(fh.read()) + entries = {e["path"]: e for e in parsed["write_files"]} + content = entries[AUTHD_SEED_PATH]["content"] + for key in ("GITEA_URL=https://gitea.tose.top", + "GITEA_USER=admin", + "SYNC_REPO=admin/chv-sync-test", + "GITEA_TOKEN="): + self.assertIn(key, content) + # 非同步键不进 authd-seed。 + self.assertNotIn("SYNC_BRANCH", content) + + def test_write_seed_authd_entry_without_sync_keys(self): + # guest-env 里没有三键时 authd-seed 只装密钥,不编默认值。 + import yaml + with tempfile.TemporaryDirectory() as tmp: + seed_dir = os.path.join(tmp, "seed") + write_seed(seed_dir, "vm1", "PRIV", "ssh-ed25519 AAAAx", + authd_env={"GITEA_TOKEN": TOKEN}) + with open(os.path.join(seed_dir, "user-data")) as fh: + parsed = yaml.safe_load(fh.read()) + entries = {e["path"]: e for e in parsed["write_files"]} + content = entries[AUTHD_SEED_PATH]["content"] + self.assertIn("GITEA_TOKEN=", content) + self.assertNotIn("GITEA_URL", content) + self.assertNotIn("GITEA_USER", content) + self.assertNotIn("SYNC_REPO", content) + # -- (4) wake 预审 ----------------------------------------------------------- class _FakeResult: diff --git a/cloud/tests/test_chv_residual.py b/cloud/tests/test_chv_residual.py index fc26da3..b0b1782 100644 --- a/cloud/tests/test_chv_residual.py +++ b/cloud/tests/test_chv_residual.py @@ -437,11 +437,14 @@ class LockfileTest(unittest.TestCase): # P2 三件套 + P3 authd 三件套 + 叶子模块 + # vsock_notify(缺 DECLARED_SOURCES 登记时 # generate 记 unresolved、verify/check 失败, - # 合成清单须覆盖才拦得住回归)。 + # 合成清单须覆盖才拦得住回归)。round-11 + # 真机修复(层 1)后叶子模块还含 errors.py + # 与 stub __init__.py。 "systemd-container", "chv-cell", "chv-broker", "chv-browser", "chv-authd", "git-credential-chv-authd", "chv-authd.service", "cloud/chv/authd.py", "cloud/chv/sentinel.py", + "cloud/chv/errors.py", "cloud/chv/__init__.py", "vsock_notify", "playwright", "chromium-headless-shell"], "versions": {"dpkg": dpkg,