Files
codex-app-server/chv-guest/chv-sync.sh
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

136 lines
5.7 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 盘投递 /etc/chv/guest-env
# (0600,root 所有),格式(KEY=VALUE,每行一项):
# GITEA_URL=https://gitea.tose.top
# GITEA_USER=<Gitea 用户名>
# GITEA_TOKEN=<专用 API token,仅此次测试/此次 VM 有效>
# SYNC_REPO=<owner/repo>
# SYNC_BRANCH=<可选,默认分支>
# GITEA_EMAIL=<可选,commit 身份邮箱;不给则留镜像占位>
# boot(root,cloud-init runcmd 调)读 guest-env,把 token 写进
# /home/chv/.git-credentials(0600,chv 所有,git credential
# store 标准机制),再以 chv 身份 clone 到 ~/workspace/<仓名>。
# pull/push/status 以 chv 身份跑,只用已落盘的 credential,不
# 再读 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}"
die() { echo "chv-sync: $*" >&2; exit 1; }
info() { echo "chv-sync: $*"; }
# 解析仓目录:显式给则用它;否则取 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}"
: "${GITEA_TOKEN:?guest-env 缺 GITEA_TOKEN}"
: "${SYNC_REPO:?guest-env 缺 SYNC_REPO}"
[ "$(id -u)" = "0" ] || die "boot 须以 root 运行(cloud-init runcmd)"
local host="${GITEA_URL#https://}"
host="${host#http://}"
host="${host%%/*}"
local name="${SYNC_REPO##*/}"
local dir="$WORKSPACE/$name"
mkdir -p "$WORKSPACE"
chown "$SYNC_USER:$SYNC_USER" "$WORKSPACE"
# token 只落盘进用户家目录的 credential store(0600),
# 不进 git remote URL、不进镜像、不进日志(此处不打印 token)。
local creds="$USER_HOME/.git-credentials"
printf 'https://%s:%s@%s\n' "$GITEA_USER" "$GITEA_TOKEN" "$host" > "$creds"
chmod 0600 "$creds"
chown "$SYNC_USER:$SYNC_USER" "$creds"
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 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 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 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