Files
codex-app-server/cloud/chv/gitea_sync.py
T
host e7f699c62b feat(chv-guest): Gitea 同步变量经 seed 进 guest-env(执行器集成)
- 新增 cloud/chv/gitea_sync.py:6 键白名单,三源合并
  (宿主 CHV_GITEA_* < 任务 gitea_sync < extra_env),
  必填 4 键缺失即 fail-closed,空即不同步
- ssh_transport:GITEA_TOKEN 进密钥集(argv 拒绝),其余
  5 键进 guest-env 集(split_guest_env)
- hostkeys.write_seed:guest_env 与 secret_env 合并写同一
  条目,仍无 runcmd、0600
- config:_gitea_fragment 产 gitea_sync_env(函数内 import,
  叶子模块顶层只许标准库)
- executor.wake_vm:起 VMM 前合并+校验(gitea_sync_incomplete),
  guest-env 随 seed 投递;provision_host_key 透传 guest_env
- worker:_run_attempt 仅对 CHV 执行器把同步变量并进 gateway_env
- 测试:cloud/tests/test_chv_gitea_sync.py 20 项
- 边界:tasks 表无 JSON 列,DB 往返丢任务 gitea_sync;
  生产主来源是宿主 CHV_GITEA_* 环境变量
2026-11-06 16:55:32 +00:00

127 lines
5.2 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.
"""Gitea 同步变量供给(CHV guest-env 集成,叶子模块)。
背景:客内 chv-sync boot 在 cloud-init 时运行、SSH 之前,
只能读 seed 投递的 /etc/chv/guest-env,看不到 SSH env 前缀。
因此同步所需的 6 个变量必须随 seed 进 guest-env,不能只靠
extra_env 的 SSH 前缀;其中 GITEA_TOKEN 是密钥,只进 seed,
绝不进 argv/日志/镜像。
变量与来源(优先级低→高,后者覆盖前者):
1. 宿主配置环境变量(主来源,生产口径):CHV_GITEA_URL、
CHV_GITEA_USER、CHV_GITEA_TOKEN、CHV_SYNC_REPO、
CHV_SYNC_BRANCH、CHV_GITEA_EMAIL。由部署该 worker/执行器
的运维在宿主环境里设置,与 CHV_SSH_KEY、CHV_EGRESS_PROXY
同款口径;CHV 是单租户单 VM 模型,一台宿主对应一个
Gitea 身份。密钥来源绝不硬编码进代码。
2. 任务级覆盖(内存态):任务 dict 的 `gitea_sync` 子 dict,
键即 6 个变量名。仅在调用方以内存态构造任务 dict 时生效
(直接调 wake_vm、测试、手工驱动);tasks 表目前没有
JSON/元数据列,DB 往返会丢掉它——这是已知边界,不做
静默声称,需要每任务不同仓库时再做任务表迁移。
3. 显式 extra_env:调用方直接传给 wake_vm/dispatch 的
extra_env 里的同名键,优先级最高。
缺失语义(fail-closed,不跳过同步):
- 6 个键一个都没有 → 本次启动不同步(chv-sync boot 见无
guest-env 静默成功,VM 照常用)。
- 任一键出现、但必填 4 键(GITEA_URL、GITEA_USER、
GITEA_TOKEN、SYNC_REPO)缺失 → 校验失败,wake 在起 VMM
之前直接拒绝;半份同步配置绝不能让 VM 带着“我以为同步
了”的状态启动。SYNC_BRANCH、GITEA_EMAIL 可选。
- 校验错误只含缺失的键名,绝不含任何变量值。
本模块是纯函数,不读文件、不跑 VM、不含任何密钥值。
"""
# 同步变量全集(guest-env 里应有的键)。
GITEA_SYNC_KEYS = frozenset({
"GITEA_URL", "GITEA_USER", "GITEA_TOKEN",
"SYNC_REPO", "SYNC_BRANCH", "GITEA_EMAIL",
})
# 其中密钥:只进 seed,绝不进 argv(见 ssh_transport 的
# _CHV_SECRET_ENV_NAMES)。
GITEA_SYNC_SECRET_KEYS = frozenset({"GITEA_TOKEN"})
# 其中非密钥、但 boot 时就要用、也必须进 guest-env 的键
#(SSH env 前缀到不了 cloud-init 时运行的 chv-sync boot)。
GITEA_SYNC_PLAIN_KEYS = GITEA_SYNC_KEYS - GITEA_SYNC_SECRET_KEYS
# 必填键:缺一即 fail-closed(见模块头缺失语义)。
GITEA_SYNC_REQUIRED_KEYS = frozenset({
"GITEA_URL", "GITEA_USER", "GITEA_TOKEN", "SYNC_REPO",
})
# 宿主配置环境变量名 → guest-env 键名。
CHV_GITEA_ENV_MAP = {
"CHV_GITEA_URL": "GITEA_URL",
"CHV_GITEA_USER": "GITEA_USER",
"CHV_GITEA_TOKEN": "GITEA_TOKEN",
"CHV_SYNC_REPO": "SYNC_REPO",
"CHV_SYNC_BRANCH": "SYNC_BRANCH",
"CHV_GITEA_EMAIL": "GITEA_EMAIL",
}
def _clean(mapping: dict | None) -> dict:
"""只留白名单键:值须为非空字符串(strip 后),其它
类型/空值/未知键一律丢掉——调用方拼错的键名绝不静默
进 seed 变成客内无人读的脏变量。"""
cleaned = {}
for key, value in dict(mapping or {}).items():
if key not in GITEA_SYNC_KEYS:
continue
if not isinstance(value, str):
continue
value = value.strip()
if not value:
continue
cleaned[key] = value
return cleaned
def config_gitea_sync_env(env: dict | None) -> dict:
"""宿主配置环境变量(CHV_GITEA_*)→ guest-env 键名。"""
mapped = {}
for env_name, key in CHV_GITEA_ENV_MAP.items():
value = (env or {}).get(env_name)
if value is not None:
mapped[key] = value
return _clean(mapped)
def task_gitea_sync_env(task: dict | None) -> dict:
"""任务 dict 的 `gitea_sync` 子 dict → 白名单后的键值。
不是 dict(或缺失)即空——绝不把任务其它字段误读成
同步变量。"""
if not isinstance(task, dict):
return {}
return _clean(task.get("gitea_sync"))
def extract_gitea_sync_env(task: dict | None = None,
extra_env: dict | None = None,
config_sync: dict | None = None) -> dict:
"""三源合并(优先级:config_sync < 任务 gitea_sync <
extra_env 同名键)。返回白名单后的 dict,可能为空
(空 = 本次启动不同步)。"""
merged = {}
merged.update(_clean(config_sync))
merged.update(task_gitea_sync_env(task))
merged.update(_clean(extra_env))
return merged
def validate_gitea_sync_env(env: dict | None) -> None:
"""校验同步变量完整性:空即不同步(通过);非空但必填
键缺失即 ValueError(只含缺失键名,不含值)。调用方
(执行器 wake_vm)在起 VMM 前调本函数做 fail-closed。"""
env = dict(env or {})
if not env:
return
missing = sorted(GITEA_SYNC_REQUIRED_KEYS - set(env))
if missing:
raise ValueError(
"Gitea 同步变量不完整,缺失:%s(同步已请求但必填"
"键不全,拒绝启动)" % ",".join(missing))