Files
codex-app-server/chv-guest/chv-sync.sh
T
host 9da74c2a78 merge(chv-muse-round12): 合入 round-11 修复轮 HEAD 50cae2a (RETEST6 全绿)
按 hunk 合入,不整文件覆盖。冲突 3 处:
- chv-guest/build.sh:取 P4 的 copy-in(cdp-broker/guard/blocklist/
  chv-memory)+ cdp-broker service/enable 三件;取 R11 的 git
  config(去掉 credential.helper store,层 5);tmpfiles 两边
  并存(/run/chv/ask-queue + /run/chv-browser)。
- chv-guest/chv-browser.py:P4 架构保留(CDP broker 客户端 +
  guard 模块);取 R11 两处仍适用的运行期修复:BROWSER_SOCK
  移进 /run/chv-browser(缺陷 4,合入后的 broker 已指该路径)
  与 handle_request 认 op=browser/action 透传;R11 的直连
  Playwright 代码(validate_url/truncate_aria/_load_playwright/
  _page snapshot)由 P4 取代,不带回。
- cloud/tests/test_chv_muse_p2.py:truncate 三测取 P4 的
  GUARD.truncate_a11y(dict 树);FakeCdpBroker 取 P4 版,删
  R11 的 FakePage 系列;干跑夹具取 R11 的 _fixture 调用,并
  给 _fixture 补 P4 四件(否则合入后 build.sh fail-fast)。

合入内移植(R11 修复在 P4 里移了位置,不移植即静默丢失):
- chv-guest/chv-cdp-broker.py:_load_playwright 返回
  sync_playwright()(原 R11 在 chv-browser.py 修过的同形
  bug);回归测试由 test_p2 移植到 test_chv_muse_p4.py。
- test_chv_muse_p2.py:HOST 由模块级 eager 改懒加载(_host()):
  host.py 顶层 import queue 会把 stdlib queue 缓进 sys.modules,
  使同进程后加载的 test_queue_sandbox 取不到 cloud/queue.py
  遮蔽而 ImportError(全量唯一失败)。

未移植(见报告):R11 的 Page.aria_snapshot() 纠正未进
chv-cdp-broker.py(仍调 accessibility.snapshot(),且 guard
的 dict 树 API 须配套改,属 P4 返工,不在本轮合入做)。

全量 cloud 套件:2522 tests OK (skipped=10),基线 a6f9597
为 2474 OK (skipped=12);+48 来自 R11 新增测试(p2 +36、
p3 +7、closeup +3、gaps +1、p4 +1 移植)。
2026-11-07 18:13:31 +00:00

176 lines
8.2 KiB
Bash
Executable File
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.
#!/bin/bash
# chv-sync:轻量 VM 里的 Gitea 双向同步(用户工作区口径)。
#
# 主角是用户,不是 agent:用户在 Gitea 上的仓库在 VM 启动时
# clone 到 ~/workspace,用完再同步回 Gitea;VM 是用户的工作区,
# Gitea 是权威远端。同步口径沿用 Git Workspace Platform 已定
# 的规则:分叉或冲突时失败关闭,不强制覆盖、不自动 rebase、
# 不静默丢提交。
#
# 凭证绝不进镜像。启动时由 seed 盘投递两份文件(0600,root 所有):
# /etc/chv/guest-env(非密钥同步变量:GITEA_URL/GITEA_USER/
# SYNC_REPO/SYNC_BRANCH/GITEA_EMAIL)与
# /etc/chv/authd-seed(feat-chv-muse-p3:只装 GITEA_TOKEN,
# 只给客内 authd 守护进程读)。
# boot(root,cloud-init runcmd 调)读 guest-env,authd 在时配
# git credential helper 走 authd 按次取凭证(只进 git 子进程
# 内存,不落盘);authd 不在时直接失败(round-11 真机修复层 5:
# 旧镜像的落盘回落已废止——它在真机测试里把 authd 释放的长效
# token 写进了客内 ~/.git-credentials;authd helper 是唯一凭证
# helper,且自身绝不落盘)。pull/push/status 以 chv 身份跑,用
# helper,不再读 guest-env;token 不会出现在 git remote URL 或
# 镜像里。
#
# 用法:
# chv-sync boot # 启动时 clone(已 clone 则静默退出,不自动 pull)
# chv-sync pull [仓目录] # 脏树拒绝;只接受 fast-forward,分叉即失败
# chv-sync push [仓目录] # 普通 push;无 upstream 时 push -u origin HEAD;
# # 非 fast-forward 即失败,绝不 --force
# chv-sync status [仓目录] # 本地状态 + 最近提交
# 仓目录省略时取 $WORKSPACE 下唯一的 git 仓库;多个时必须显式给。
set -euo pipefail
GUEST_ENV="${CHV_GUEST_ENV:-/etc/chv/guest-env}"
WORKSPACE="${CHV_WORKSPACE:-/home/chv/workspace}"
USER_HOME="${CHV_USER_HOME:-/home/chv}"
SYNC_USER="${CHV_USER:-chv}"
# privsep worker(feat-chv-muse-p6):socket 在时 pull/push/status
# 改走 worker(类型化请求),不在时走旧的直接路径 + 警告(过渡兼容,
# 旧镜像退役后改为无 worker 即 fail-closed)。
WORKER_SOCK="${CHV_GITEA_WORKER_SOCK:-/run/chv-gitea/worker.sock}"
WORKER_CLIENT="${CHV_GITEA_WORKER_CLIENT:-/usr/local/bin/chv-sync-worker-client}"
die() { echo "chv-sync: $*" >&2; exit 1; }
info() { echo "chv-sync: $*"; }
# 试走 worker:返回 42 = worker 不在(调用方走旧路径);其它返回
# 值即 worker 的退出码(调用方直接退出,不再回落重跑)。
try_worker() {
local action="$1"; shift
[ -S "$WORKER_SOCK" ] || return 42
[ -x "$WORKER_CLIENT" ] || return 42
"$WORKER_CLIENT" "$action" "$@"
}
# 解析仓目录:显式给则用它;否则取 WORKSPACE 下唯一的 git 仓库。
resolve_repo() {
if [ -n "${1:-}" ]; then
[ -d "$1/.git" ] || die "不是 git 仓库:$1"
printf '%s' "$1"
return 0
fi
local found=()
local d
for d in "$WORKSPACE"/*/; do
[ -d "${d}.git" ] && found+=("${d%/}")
done
if [ "${#found[@]}" -eq 1 ]; then
printf '%s' "${found[0]}"
return 0
fi
die "WORKSPACE 下 git 仓库不是唯一(${#found[@]} 个),请显式给仓目录"
}
cmd_boot() {
# 无 guest-env = 本次启动不同步任何仓,静默成功(VM 照常用)。
[ -f "$GUEST_ENV" ] || { info "无 $GUEST_ENV,本次启动不同步"; exit 0; }
# shellcheck disable=SC1090
set -a; . "$GUEST_ENV"; set +a
: "${GITEA_URL:?guest-env 缺 GITEA_URL}"
: "${GITEA_USER:?guest-env 缺 GITEA_USER}"
: "${SYNC_REPO:?guest-env 缺 SYNC_REPO}"
[ "$(id -u)" = "0" ] || die "boot 须以 root 运行(cloud-init runcmd)"
local name="${SYNC_REPO##*/}"
local dir="$WORKSPACE/$name"
mkdir -p "$WORKSPACE"
chown "$SYNC_USER:$SYNC_USER" "$WORKSPACE"
# feat-chv-muse-p3:authd 路径——socket 存在时凭证走 helper,
# 不再把真 token 写进 .git-credentials 落盘;helper 按次向
# authd 取凭证,只进 git 子进程内存。socket 不存在时直接失败
#(round-11 真机修复层 5:落盘回落已废止,见下)。
# 注意:authd 路径下 GITEA_TOKEN 不在 guest-env 里(它只进
# authd-seed 给守护进程),所以这里不要求它。
local authd_socket="${CHV_AUTHD_SOCKET:-/run/chv-authd/authd.sock}"
local helper="/usr/local/bin/git-credential-chv-authd"
if [ -S "$authd_socket" ] && [ -x "$helper" ]; then
runuser -u "$SYNC_USER" -- env HOME="$USER_HOME" git config --global credential.helper "$helper"
# helper 按路径推断仓库与操作(Sentinel L7 绑定),git
# 默认不把 path 发给 helper(须 credential.useHttpPath),
# 不开它每次都会因“请求不完整”拒绝——新测试抓到的真问题。
runuser -u "$SYNC_USER" -- env HOME="$USER_HOME" git config --global credential.useHttpPath true
info "凭证走 chv-authd(不落盘)"
else
# round-11 真机修复(层 5):authd 不在时直接失败,不再
# 把真 token 写进 ~/.git-credentials 落盘——旧路径在
# round-11 真机测试里把 authd 释放的长效 token 写进了
# 客内磁盘(/home/chv/.git-credentials,0600)。authd
# helper 是唯一凭证 helper,且自身绝不落盘;旧镜像退役
# 后过渡兼容即废止,不再留落盘逃生口。
die "chv-authd 未运行(socket 或 helper 缺失),拒绝同步:凭证绝不落盘,请用含 authd 的镜像"
fi
if [ -d "$dir/.git" ]; then
info "已 clone:$dir(不自动 pull,改动由用户显式同步)"
exit 0
fi
[ -e "$dir" ] && die "$dir 已存在但不是 git 仓库,请人工处理"
local clone_args=(clone)
[ -n "${SYNC_BRANCH:-}" ] && clone_args+=(--branch "$SYNC_BRANCH")
clone_args+=("$GITEA_URL/$SYNC_REPO.git" "$dir")
runuser -u "$SYNC_USER" -- env HOME="$USER_HOME" git "${clone_args[@]}"
runuser -u "$SYNC_USER" -- env HOME="$USER_HOME" git -C "$dir" config user.name "$GITEA_USER"
if [ -n "${GITEA_EMAIL:-}" ]; then
runuser -u "$SYNC_USER" -- env HOME="$USER_HOME" git -C "$dir" config user.email "$GITEA_EMAIL"
fi
info "已 clone $SYNC_REPO 到 $dir,HEAD=$(runuser -u "$SYNC_USER" -- env HOME="$USER_HOME" git -C "$dir" rev-parse --short HEAD)"
}
cmd_pull() {
local rc=0
try_worker pull "${1:-}" || rc=$?
[ "$rc" != "42" ] && exit "$rc"
echo "chv-sync: worker 不在,走旧的直接路径(过渡兼容)" >&2
local dir
dir="$(resolve_repo "${1:-}")"
[ -z "$(git -C "$dir" status --porcelain)" ] \
|| die "工作树有未提交改动,拒绝 pull(先 commit 或 stash)"
git -C "$dir" pull --ff-only \
|| die "pull 不是 fast-forward(本地与远端已分叉),请人工处理,不自动 rebase/merge"
info "已同步:$dir HEAD=$(git -C "$dir" rev-parse --short HEAD)"
}
cmd_push() {
local rc=0
try_worker push "${1:-}" || rc=$?
[ "$rc" != "42" ] && exit "$rc"
echo "chv-sync: worker 不在,走旧的直接路径(过渡兼容)" >&2
local dir
dir="$(resolve_repo "${1:-}")"
if git -C "$dir" rev-parse --abbrev-ref --symbolic-full-name '@{u}' >/dev/null 2>&1; then
git -C "$dir" push \
|| die "push 被拒绝(远端已有新提交,先 pull 再 push,不 --force)"
else
git -C "$dir" push -u origin HEAD \
|| die "push 被拒绝(远端已有新提交,先 pull 再 push,不 --force)"
fi
info "已 push:$dir HEAD=$(git -C "$dir" rev-parse --short HEAD)"
}
cmd_status() {
local rc=0
try_worker status "${1:-}" || rc=$?
[ "$rc" != "42" ] && exit "$rc"
echo "chv-sync: worker 不在,走旧的直接路径(过渡兼容)" >&2
local dir
dir="$(resolve_repo "${1:-}")"
git -C "$dir" status -sb
git -C "$dir" log --oneline -5
}
case "${1:-}" in
boot) shift; cmd_boot "$@" ;;
pull) shift; cmd_pull "$@" ;;
push) shift; cmd_push "$@" ;;
status) shift; cmd_status "$@" ;;
*) echo "用法:chv-sync boot|pull|push|status [仓目录]" >&2; exit 2 ;;
esac