feat(chv-guest): seed 附加项供客机用户与静态网络(wake 路径前置)

wake_vm 在 WSL 台架上跑通需要客机身份与网络:镜像本身不建
用户、tap 无 DHCP,缺 users 块的 VM 登不进去、缺 network-config
的 VM 拿不到静态 IP。最小补法(复用 write_seed 机制):
- hostkeys.write_seed 新增 user_data_extra(原样追加进
  user-data,供 users: 块)与 network_config(写 seed
  network-config 文件,0600);附加项含 runcmd: 即 ValueError
- config _seed_fragment:CHV_SEED_USER_DATA_EXTRA_FILE /
  CHV_SEED_NETWORK_CONFIG_FILE(只存路径)
- executor._seed_extras 在 wake 起 VMM 前读入,缺失即
  ChvFailure seed_extra_missing;provision_host_key 透传
- README §7 残留更新:guest-env 集成已做
- 测试:test_chv_gitea_sync 新增 5 项(写盘/禁 runcmd/
  配置映射/wake 带附加项/缺失 fail-closed)
This commit is contained in:
host committed 2026-11-06 16:58:27 +00:00
1 parent e7f699c62b
commit d7cc482ed5
5 files changed
+184 -11

No files matched your search

+40 -6
View File
@@ -400,7 +400,8 @@ class CloudHypervisorExecutor(Executor):
"single_tenant_project", "snapshot_max_count",
"snapshot_max_total_gb", "snapshot_retention_days",
"snapshot_dir", "allow_remote_snapshot",
"gitea_sync_env"):
"gitea_sync_env", "seed_user_data_extra_file",
"seed_network_config_file"):
setattr(self, _field, getattr(_cfg, _field))
# -- 名字 / 路径 ---------------------------------------------------
@@ -830,7 +831,9 @@ class CloudHypervisorExecutor(Executor):
host=ssh_host)])
def provision_host_key(self, task: dict, secret_env: dict | None = None,
guest_env: dict | None = None) -> dict:
guest_env: dict | None = None,
user_data_extra: str | None = None,
network_config: str | None = None) -> dict:
"""按 VM 预置 host key(补缺口轨强方案,见 hostkeys.py):
启动前为该 VM 生成一次性 ed25519 host key 对,公钥
立即写进该 VM 的 known_hosts(钉死),私钥只写进
@@ -868,7 +871,9 @@ class CloudHypervisorExecutor(Executor):
seed_dir = write_seed(paths["seed_dir"], vm,
private_text, public_text,
secret_env=secret_env,
guest_env=guest_env)
guest_env=guest_env,
user_data_extra=user_data_extra,
network_config=network_config)
self._emit("host_key_generated", task,
"vm=%s secret_names=%s"
% (vm, ",".join(sorted(secret_env or {}))))
@@ -1311,6 +1316,30 @@ class CloudHypervisorExecutor(Executor):
"VMM api-socket 组/其它可写(%o),拒绝启动:%s"
% (mode, socket_path))
def _seed_extras(self) -> tuple:
"""读 seed 附加项文件(CHV_SEED_* 配置的路径):
返回 (user_data_extra, network_config),皆空即
(None, None)。路径已配但文件缺失/读不出时
ChvFailure(seed_extra_missing),wake 在起 VMM
之前拒绝——缺 users 块的 VM 起了也登不进去,缺
network-config 的 VM 在无 DHCP 的 tap 上拿不到 IP,
不如不起。错误只含路径,不含文件内容。"""
out = []
for attr in ("seed_user_data_extra_file",
"seed_network_config_file"):
path = getattr(self, attr, None)
if not path:
out.append(None)
continue
try:
with open(path) as fh:
out.append(fh.read())
except OSError as exc:
raise ChvFailure(
"seed_extra_missing",
"seed 附加文件读不出:%s:%s" % (path, exc))
return out[0], out[1]
def _effective_extra_env(self, task: dict,
extra_env: dict | None) -> dict | None:
"""wake 生效的 extra_env:把 Gitea 同步变量并进来。
@@ -1381,7 +1410,8 @@ class CloudHypervisorExecutor(Executor):
return secrets, net_entries
def _provision_seed_disk(self, task: dict, name: str, secrets,
guest_env: dict | None = None) -> str | None:
guest_env: dict | None = None,
seed_extras: tuple | None = None) -> str | None:
"""provisioned 模式的 seed 盘供给:生成→制盘→挂盘,
任一步失败即抛(调用方 stop_vm 清理),纯移动。"""
seed_disk = None
@@ -1403,9 +1433,11 @@ class CloudHypervisorExecutor(Executor):
delete_host_private_key,
seed_disk_path,
)
extra, netcfg = seed_extras or (None, None)
provisioned = self.provision_host_key(
task, secret_env=secrets or None,
guest_env=guest_env or None)
guest_env=guest_env or None,
user_data_extra=extra, network_config=netcfg)
seed_disk = seed_disk_path(self.state_dir, name)
build_seed_disk(
provisioned["seed_dir"], seed_disk,
@@ -1487,6 +1519,7 @@ class CloudHypervisorExecutor(Executor):
name = self.vm_name(task)
try:
extra_env = self._effective_extra_env(task, extra_env)
seed_extras = self._seed_extras()
except ChvFailure as exc:
self._emit("vm_failed", task,
"vm=%s wake 失败 kind=%s" % (name, exc.kind),
@@ -1497,7 +1530,8 @@ class CloudHypervisorExecutor(Executor):
task_disk, socket_path = self._wake_prepare(task, name)
try:
seed_disk = self._provision_seed_disk(
task, name, secrets, guest_env=guest_env or None)
task, name, secrets, guest_env=guest_env or None,
seed_extras=seed_extras)
self._boot_vm(task, name, task_disk, seed_disk, net_entries)
except Exception:
try: