- Chromium 钉死:CHROMIUM_HEADLESS_SHELL_REV(纯数字修订号) 设即钉死,buildinfo.py 校验读回的 ms-playwright 缓存目录名 须恰好是该修订号,否则构建失败;generate/check 的 --verify 也复核;未设时只记录、构建输出明说未钉死,BUILDINFO 记 null - /app 最小集:build.sh 不再把整个 $REPO_DIR/cloud/ 拷进客机, 只拷 host.py(只用标准库,不依赖 cloud/);REPO_DIR 预检 不再要求 cloud/ 存在 - generate_lockfile.py 只读 BUILDINFO.versions 一事已在 to10 轨 改写,本棒以合成 BUILDINFO 干跑复核(假版本 9.9.9 进 lockfile, 宿主 git 1:2.43.0 未混入)并补钉死复核;真实 lockfile 产出 仍待 V-9 真机构建,不编造版本号
335 lines
20 KiB
Bash
Executable File
335 lines
20 KiB
Bash
Executable File
#!/bin/bash
|
||
# chv-guest 轻量镜像构建脚本(2026-11-06 用户定:停掉 5.5G 全量版,
|
||
# 按轻量口径重开;不再对齐 Manus,不做三面板/多租户/生产级全套)。
|
||
#
|
||
# 底座:Ubuntu Minimal 24.04 官方 qcow2(248MB,
|
||
# https://cloud-images.ubuntu.com/minimal/releases/noble/release/
|
||
# ubuntu-24.04-minimal-cloudimg-amd64.img——minimal 发布在 noble
|
||
# 代号目录下,不是 releases/24.04)。只装 codex 0.164.0-alpha.9
|
||
# linux-x64 + Python + SSH + cloud-init + 一个薄客机 agent
|
||
# (/app/host.py,仅此一个文件——host.py 只用标准库,不依赖
|
||
# cloud/;旧版把整个 $REPO_DIR/cloud/ 拷进 /app/,客内 agent
|
||
# 可读全套 server/worker 代码,M4 收尾已收紧为最小集)。
|
||
# 浏览器:Playwright +
|
||
# chromium-headless-shell(不要完整 Chromium);字体只要最小的
|
||
# fonts-wqy-microhei(中文能看),不要全套 fonts-noto-cjk。
|
||
# 目标:qcow2 实际占用 ≤1GB,raw ≤2GB(见下注)。
|
||
# 控制面只留最小的:现有 cloud/chv/executor.py 起停 VM + SSH
|
||
# 进去调 codex + Playwright 截图。
|
||
#
|
||
# Gitea 双向同步(用户工作区口径,2026-11-06 追加):镜像里加
|
||
# git + gt 1.0.0(官方 install.sh + SHA256 校验,跟 170/172
|
||
# 同款),配好 git user 与 credential helper 占位,不含真实
|
||
# token;客机启动时 /usr/local/bin/chv-sync boot 按 seed 盘
|
||
# /etc/chv/guest-env(0600)把用户选定的仓 clone 到
|
||
# ~/workspace,后续 pull/push 由用户显式同步,分叉/冲突时
|
||
# 失败关闭(不 --force、不自动 rebase)。token 绝不进镜像、
|
||
# 代码、日志。
|
||
#
|
||
# 用法(必须在带 /dev/kvm 的宿主上跑全量模式):
|
||
# CODEX_BIN_DIR=bin UBUNTU_VERSION=24.04 CODEX_VERSION=0.164.0-alpha.9 \
|
||
# REPO_COMMIT=$(git rev-parse --short HEAD) \
|
||
# UBUNTU_IMAGE_SHA256=<minimal 镜像 sha256> ./build.sh out/
|
||
# ./build.sh --dry-run out/ # 只打印命令,不执行、不要求 KVM
|
||
#
|
||
# 产物(out/ 内):
|
||
# vmlinux # virt-get-kernel 从云镜像取出的内核(CHV --kernel 指向它)
|
||
# disk.raw # qemu-img 转成的 raw 根文件系统镜像(CHV_ROOTFS 指向它)
|
||
# BUILDINFO.json # {tag, ubuntu, codex_version, repo_commit, built_at, contents}
|
||
# + versions {dpkg, pip, chromium, gt}(构建时从客机读回的实际版本)+
|
||
# ubuntu_image_sha256_set(SHA256 必填,见下)
|
||
# 绝不进镜像的东西:任何模型 key、系统 key、用户账号数据、
|
||
# 生产凭证;模型 key 由客机 cloud-init 预置的运行环境变量名
|
||
# 注入,不写进镜像层。
|
||
# feat-chv-secfix(M4/M8):UBUNTU_IMAGE_SHA256 必填;
|
||
# sshd 加固由构建写入 /etc/ssh/sshd_config.d/99-chv.conf
|
||
# 并经 sshd -t + grep 断言(README 的“只允许 key 登录”
|
||
# 从此是构建断言,不再只是文档声称)。
|
||
# B-10:virt-customize 的 chroot 里 sshd -t 须先 ssh-keygen -A
|
||
# 临时生成 host key 并 mkdir -p /run/sshd,否则分别报无 host
|
||
# key / 缺 privilege separation 目录而失败;断言通过后立即删
|
||
# 临时 key,镜像仍不带 host key(per-VM 预置才生效)。
|
||
#
|
||
# 客机 host key 硬性要求(见 README §5,补缺口轨新增):
|
||
# 客机必须装 cloud-init 且支持 NoCloud seed,首启先应用
|
||
# seed 再启动 sshd,且不得无条件重新生成 host key,否则
|
||
# 执行器的 per-VM 预置钉死即失效。本脚本在镜像里写
|
||
# /etc/cloud/cloud.cfg.d/99-chv-hostkey.cfg:ssh_deletekeys:
|
||
# false(绝不删已有 key)+ 首启 bootcmd 只在缺 key 时
|
||
# ssh-keygen -A(bootcmd 跑在 seed write_files 之前,预置
|
||
# key 随后覆盖生成值,最终仍是预置 key)。
|
||
# B-23:曾写 ssh_genkeytypes: [],彻底关掉首启生成;Minimal
|
||
# 底座 cloud.cfg 又不含 ssh 模块,客机无 host key、sshd -t
|
||
# 失败、ssh.service 连败,SSH 全程不通——[] 已删,绝不再写。
|
||
#
|
||
# 注:Minimal 底座虚拟盘 3.5G(根分区 2.4G,实测 1.9G 空闲),
|
||
# qemu-img convert 出的 disk.raw 表观 3.5G、实际占用约 0.6G
|
||
# (稀疏)。raw 表观 ≤2G 须在构建后另做一步收窄(guestfish
|
||
# 缩根文件系统 + sgdisk 重建 1 号分区 + qemu-img resize
|
||
# --shrink),不在本脚本内做——收窄前先确认装完后根分区实际
|
||
# 占用,避免把镜像收到无法启动;qcow2 压缩产物是首要验收项。
|
||
set -euo pipefail
|
||
|
||
DRY_RUN=0
|
||
if [ "${1:-}" = "--dry-run" ]; then
|
||
DRY_RUN=1
|
||
shift
|
||
fi
|
||
OUT="${1:-out}"
|
||
: "${CODEX_BIN_DIR:?CODEX_BIN_DIR 未设:需 npm 安装 @openai/codex 后的配对二进制目录}"
|
||
: "${UBUNTU_VERSION:?UBUNTU_VERSION 未设}"
|
||
: "${CODEX_VERSION:?CODEX_VERSION 未设}"
|
||
: "${REPO_COMMIT:?REPO_COMMIT 未设:构建时仓库 commit 短 hash}"
|
||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||
REPO_DIR="${REPO_DIR:-$(cd "$SCRIPT_DIR/.." && pwd)}"
|
||
# gt 1.0.0:官方 install.sh(gitea.com/gitea/cli,main 分支
|
||
# scripts/install.sh),脚本本身下载 dl.gitea.com 的二进制并
|
||
# 验 SHA256;此处再验脚本自身的 SHA256(2026-11-06 实测值,
|
||
# 上游轮换脚本后构建会明确失败,不静默跑新脚本)。
|
||
GT_VERSION="${GT_VERSION:-1.0.0}"
|
||
# feat-chv-closeup(M4):Chromium 单独钉死。PLAYWRIGHT=1 时
|
||
# ms-playwright 缓存目录名即 Chromium 版本标识(如
|
||
# chromium_headless_shell-1248),此前只记录不钉死——playwright
|
||
# 一升级,目录名跟着变,构建静默换浏览器版本。
|
||
# CHROMIUM_HEADLESS_SHELL_REV 设为纯数字修订号(如 1248)即钉死:
|
||
# buildinfo.py 校验读回的目录名须恰好是该修订号,否则构建失败。
|
||
# 未设时只记录、不强制,并在构建输出里明说“未钉死”,绝不假装
|
||
# 已钉死;真机 V-9 验收须用已设该变量的构建。
|
||
CHROMIUM_HEADLESS_SHELL_REV="${CHROMIUM_HEADLESS_SHELL_REV:-}"
|
||
if [ -n "$CHROMIUM_HEADLESS_SHELL_REV" ] && ! printf '%s' "$CHROMIUM_HEADLESS_SHELL_REV" | grep -Eq '^[0-9]+$'; then
|
||
echo "CHROMIUM_HEADLESS_SHELL_REV 须为纯数字修订号(如 1248):$CHROMIUM_HEADLESS_SHELL_REV" >&2; exit 1
|
||
fi
|
||
GT_INSTALL_SH_SHA256="${GT_INSTALL_SH_SHA256:-cf82acecc0e3ffa6f690d33902eefcf1cc21a5b11004103bf23c4436664288d5}"
|
||
# B-03/B-04:输入格式校验(干跑与全量都做,报错点提前,
|
||
# 不等到 curl 404 或 sha256sum -c 才失败)。
|
||
if ! printf '%s' "$UBUNTU_VERSION" | grep -Eq '^[0-9]+\.[0-9]+$'; then
|
||
echo "UBUNTU_VERSION 格式须为 NN.NN:$UBUNTU_VERSION" >&2; exit 1
|
||
fi
|
||
if ! printf '%s' "$REPO_COMMIT" | grep -Eq '^[0-9a-f]{7,40}$'; then
|
||
echo "REPO_COMMIT 须为 7-40 位 hex:$REPO_COMMIT" >&2; exit 1
|
||
fi
|
||
if ! printf '%s' "$CODEX_VERSION" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+'; then
|
||
echo "CODEX_VERSION 格式异常:$CODEX_VERSION" >&2; exit 1
|
||
fi
|
||
if ! printf '%s' "$GT_VERSION" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+$'; then
|
||
echo "GT_VERSION 格式异常:$GT_VERSION" >&2; exit 1
|
||
fi
|
||
if ! printf '%s' "$GT_INSTALL_SH_SHA256" | grep -Eq '^[0-9a-f]{64}$'; then
|
||
echo "GT_INSTALL_SH_SHA256 须为 64 位 hex" >&2; exit 1
|
||
fi
|
||
if [ -n "${UBUNTU_IMAGE_SHA256:-}" ] && ! printf '%s' "$UBUNTU_IMAGE_SHA256" | grep -Eq '^[0-9a-f]{64}$'; then
|
||
echo "UBUNTU_IMAGE_SHA256 须为 64 位 hex" >&2; exit 1
|
||
fi
|
||
# B-09:版本声称须核对,不只进 tag。codex 二进制真实版本
|
||
# 须包含 CODEX_VERSION;REPO_COMMIT 须等于仓库 HEAD 短 hash;
|
||
# 工作树脏即拒绝构建(tag 会谎称干净 commit)。
|
||
CODEX_ACTUAL_VERSION="$("$CODEX_BIN_DIR/codex" --version 2>/dev/null || true)"
|
||
case "$CODEX_ACTUAL_VERSION" in
|
||
*"$CODEX_VERSION"*) ;;
|
||
*) echo "codex 二进制版本与 CODEX_VERSION 不符:got [$CODEX_ACTUAL_VERSION] want 含 [$CODEX_VERSION]" >&2; exit 1 ;;
|
||
esac
|
||
HEAD_SHORT="$(git -C "$REPO_DIR" rev-parse --short HEAD)"
|
||
if [ "$HEAD_SHORT" != "$REPO_COMMIT" ]; then
|
||
echo "REPO_COMMIT 与仓库 HEAD 不符:$REPO_COMMIT != $HEAD_SHORT" >&2; exit 1
|
||
fi
|
||
if [ -n "$(git -C "$REPO_DIR" status --porcelain)" ]; then
|
||
echo "仓库工作树有未提交修改,拒绝构建(tag 会谎称干净 commit)" >&2; exit 1
|
||
fi
|
||
TAG="chv-guest-${UBUNTU_VERSION}-${CODEX_VERSION}-${REPO_COMMIT}"
|
||
UBUNTU_IMAGE_URL="${UBUNTU_IMAGE_URL:-https://cloud-images.ubuntu.com/minimal/releases/noble/release/ubuntu-${UBUNTU_VERSION}-minimal-cloudimg-amd64.img}"
|
||
|
||
run() {
|
||
if [ "$DRY_RUN" = "1" ]; then
|
||
echo "DRY-RUN: $*"
|
||
else
|
||
echo "+ $*"
|
||
"$@"
|
||
fi
|
||
}
|
||
|
||
# 参数校验(干跑与全量都做,不依赖 KVM)。
|
||
for bin in codex codex-code-mode-host; do
|
||
if [ ! -x "$CODEX_BIN_DIR/$bin" ]; then
|
||
echo "缺少配对二进制:$CODEX_BIN_DIR/$bin,构建失败" >&2
|
||
exit 1
|
||
fi
|
||
done
|
||
if [ ! -d "$CODEX_BIN_DIR/codex-resources" ]; then
|
||
echo "缺少 codex-resources(bwrap 载体),构建失败" >&2
|
||
exit 1
|
||
fi
|
||
if [ ! -f "$REPO_DIR/host.py" ]; then
|
||
echo "REPO_DIR 不是 codex-app-server 仓库根(缺 host.py):$REPO_DIR" >&2
|
||
exit 1
|
||
fi
|
||
if [ ! -f "$SCRIPT_DIR/chv-sync.sh" ]; then
|
||
echo "缺少 chv-sync.sh(Gitea 双向同步脚本):$SCRIPT_DIR" >&2
|
||
exit 1
|
||
fi
|
||
if [ "$DRY_RUN" = "0" ]; then
|
||
if [ ! -e /dev/kvm ]; then
|
||
echo "CHV_GUEST_BUILD needs a KVM host: /dev/kvm 不存在" >&2
|
||
exit 1
|
||
fi
|
||
for tool in curl qemu-img virt-customize virt-get-kernel python3 virt-cat; do
|
||
if ! command -v "$tool" >/dev/null 2>&1; then
|
||
echo "缺少构建工具:$tool,构建失败" >&2
|
||
exit 1
|
||
fi
|
||
done
|
||
fi
|
||
|
||
run mkdir -p "$OUT"
|
||
# B-07:磁盘空间预检(仅全量模式;干跑不建目录,无法 df)。
|
||
# 轻量版实际占用约 2G(底座 248M + disk.raw 稀疏约 0.6G +
|
||
# 内核约 46M + 构建余量),阈值从全量版 20G 降到 8G。
|
||
if [ "$DRY_RUN" = "0" ]; then
|
||
avail_kb="$(df -k --output=avail "$OUT" | tail -1 | tr -d ' ')"
|
||
if [ "${avail_kb:-0}" -lt 8388608 ]; then
|
||
echo "磁盘不足:$OUT 所在盘可用 ${avail_kb}KB,需 ≥8GB" >&2; exit 1
|
||
fi
|
||
fi
|
||
IMG="$OUT/ubuntu-${UBUNTU_VERSION}-minimal-cloudimg-amd64.img"
|
||
# feat-chv-secfix(M4):云镜像 SHA256 改为必填(干跑只验
|
||
# “已设”,不验文件;全量模式下载后验哈希)。未设即失败,
|
||
# 不再只依赖 HTTPS 传输层作唯一锚。
|
||
if [ -z "${UBUNTU_IMAGE_SHA256:-}" ]; then
|
||
echo "UBUNTU_IMAGE_SHA256 未设:云镜像须以哈希锚定,构建失败" >&2
|
||
exit 1
|
||
fi
|
||
if [ "$DRY_RUN" = "1" ]; then
|
||
echo "DRY-RUN: curl -fL --retry 3 --retry-all-errors --connect-timeout 30 $UBUNTU_IMAGE_URL -o $IMG"
|
||
echo "DRY-RUN: [ -s $IMG ] || exit 1"
|
||
echo "DRY-RUN: sha256sum -c (UBUNTU_IMAGE_SHA256)"
|
||
else
|
||
if [ ! -s "$IMG" ]; then
|
||
curl -fL --retry 3 --retry-all-errors --connect-timeout 30 "$UBUNTU_IMAGE_URL" -o "$IMG"
|
||
fi
|
||
[ -s "$IMG" ] || { echo "下载的云镜像为空:$IMG" >&2; exit 1; }
|
||
# B-06:哈希不符时删文件重下一次,不直接退出让操作员手动清理。
|
||
if ! echo "$UBUNTU_IMAGE_SHA256 $IMG" | sha256sum -c -; then
|
||
echo "云镜像哈希不符,删除后重下一次:$IMG" >&2
|
||
rm -f "$IMG"
|
||
curl -fL --retry 3 --retry-all-errors --connect-timeout 30 "$UBUNTU_IMAGE_URL" -o "$IMG"
|
||
[ -s "$IMG" ] || { echo "重下的云镜像为空:$IMG" >&2; exit 1; }
|
||
echo "$UBUNTU_IMAGE_SHA256 $IMG" | sha256sum -c -
|
||
fi
|
||
fi
|
||
run qemu-img convert -f qcow2 -O raw "$IMG" "$OUT/disk.raw"
|
||
# 轻量版不扩盘:Minimal 底座根分区 2.4G、实测 1.9G 空闲,
|
||
# 轻量清单(见下)装完仍有余量;全量版 B-16 的 +12G 扩盘
|
||
# 已删除——扩盘会把 raw 表观顶到 16G,与 raw ≤2G 目标直接冲突。
|
||
# B-21:apt 必须 --no-install-recommends。virt-customize
|
||
# --install 默认装 Recommends,python3-pip 会连带拉入
|
||
# python3-dev → gcc/g++/build-essential 全套工具链,根分区
|
||
# 2.4G 被顶到 96%(实测 2.2G/95M 空闲),Playwright 下载
|
||
# headless-shell 到 70% 时 ENOSPC 失败(round 6)。改走
|
||
# --run-command apt-get install --no-install-recommends,
|
||
# 并把会被 Recommends 带进来的必需项显式列出:fontconfig
|
||
# (字体发现,没有它 Chromium 找不到中文字体)、openssh-client
|
||
# (git 的 Recommends,agent 任务可能要 ssh 出站)。
|
||
# apt-get clean 只清 .deb 缓存、不动 lists(playwright
|
||
# --with-deps 稍后还要 apt 装浏览器依赖);lists 在最后一步清。
|
||
# B-22:virt-customize 的客机 shell 里 HOME=/,Playwright 默认
|
||
# 缓存路径 ~/.cache/ms-playwright 会落到根目录 /.cache(round 7
|
||
# 实测:浏览器装在 /.cache/ms-playwright/chromium_headless_shell-1248,
|
||
# /root/.cache 根本不存在,ls 读回空文件、BUILDINFO.versions.chromium
|
||
# 为空,check_lockfile 兜底报未覆盖)。改走系统路径
|
||
# PLAYWRIGHT_BROWSERS_PATH=/usr/local/share/ms-playwright:构建时
|
||
# 装这里、读回这里;运行时 agent 也须设同一变量,否则 chv 用户
|
||
# 会在自己家目录下找不到浏览器。
|
||
# Playwright 默认开启(PLAYWRIGHT=0 可显式关掉),浏览器只装
|
||
# chromium-headless-shell(不要完整 Chromium)。单次
|
||
# virt-customize:apt 安装、pip、拷入、sshd 加固、git 占位、
|
||
# gt 安装、chv-sync、版本读回都在同一次定制里按参数顺序跑;
|
||
# Playwright 装在第一次 freeze 之后、最终 freeze 之前(B-08:
|
||
# 最终 freeze 须在 Playwright 安装之后,否则
|
||
# BUILDINFO.versions.pip 里没有 playwright;Chromium 版本以
|
||
# ms-playwright 缓存目录名记录(如 chromium_headless_shell-1248),
|
||
# 写进 /app/guest-versions-chromium.txt)。
|
||
GT_INSTALL_CMD="curl -fsSL -o /tmp/gt-install.sh https://gitea.com/gitea/cli/raw/branch/main/scripts/install.sh && echo \"${GT_INSTALL_SH_SHA256} /tmp/gt-install.sh\" | sha256sum -c - && GITEA_CLI_VERSION=${GT_VERSION} GITEA_CLI_INSTALL_DIR=/usr/local/bin sh /tmp/gt-install.sh && rm -f /tmp/gt-install.sh && /usr/local/bin/gt version"
|
||
VC_ARGS=(-a "$OUT/disk.raw"
|
||
--run-command 'apt-get update -qq && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends git openssh-server openssh-client cloud-init python3 python3-pip curl ca-certificates xz-utils fonts-wqy-microhei fontconfig && apt-get clean'
|
||
--copy-in "$CODEX_BIN_DIR/codex:/usr/local/bin/"
|
||
--copy-in "$CODEX_BIN_DIR/codex-code-mode-host:/usr/local/bin/"
|
||
--copy-in "$CODEX_BIN_DIR/codex-resources:/usr/local/bin/"
|
||
--run-command 'chmod +x /usr/local/bin/codex /usr/local/bin/codex-code-mode-host; ln -sf /usr/local/bin/codex-resources/bwrap /usr/local/bin/bwrap'
|
||
--mkdir /app
|
||
--copy-in "$REPO_DIR/host.py:/app/"
|
||
--copy-in "$SCRIPT_DIR/chv-sync.sh:/usr/local/bin/"
|
||
--run-command 'mv /usr/local/bin/chv-sync.sh /usr/local/bin/chv-sync && chmod +x /usr/local/bin/chv-sync'
|
||
--run-command 'printf "runcmd:\n - systemctl restart ssh || service ssh restart || true\n - /usr/local/bin/chv-sync boot >>/var/log/chv-sync.log 2>&1 || true\n" > /etc/cloud/cloud.cfg.d/99-chv-sync.cfg'
|
||
--run-command 'git config --system user.name chv && git config --system user.email chv@localhost && git config --system credential.helper store && git config --system init.defaultBranch main'
|
||
--run-command "$GT_INSTALL_CMD"
|
||
--run-command '/usr/local/bin/gt version > /app/guest-versions-gt.txt'
|
||
--run-command 'printf "ssh_deletekeys: false\nbootcmd:\n - \"[ -f /etc/ssh/ssh_host_ed25519_key ] || ssh-keygen -A\"\n" > /etc/cloud/cloud.cfg.d/99-chv-hostkey.cfg; systemctl enable ssh || true'
|
||
--run-command 'printf "PasswordAuthentication no\nPermitRootLogin prohibit-password\nChallengeResponseAuthentication no\nKbdInteractiveAuthentication no\nPubkeyAuthentication yes\n" > /etc/ssh/sshd_config.d/99-chv.conf && mkdir -p /run/sshd && ssh-keygen -A && sshd -t && grep -q "^PasswordAuthentication no" /etc/ssh/sshd_config.d/99-chv.conf && grep -q "^PermitRootLogin prohibit-password" /etc/ssh/sshd_config.d/99-chv.conf && rm -f /etc/ssh/ssh_host_* && test -z "$(ls /etc/ssh/ssh_host_* 2>/dev/null)"'
|
||
--run-command 'dpkg-query -W -f "\${Package} \${Version}\n" > /app/guest-versions-dpkg.txt; pip3 freeze > /app/guest-versions-pip.txt || true')
|
||
if [ "${PLAYWRIGHT:-1}" = "1" ]; then
|
||
VC_ARGS+=(
|
||
--run-command '(pip3 install --no-cache-dir --break-system-packages playwright || pip3 install --no-cache-dir playwright) && PLAYWRIGHT_BROWSERS_PATH=/usr/local/share/ms-playwright python3 -m playwright install --with-deps chromium-headless-shell'
|
||
--run-command 'dpkg-query -W -f "\${Package} \${Version}\n" > /app/guest-versions-dpkg.txt; pip3 freeze > /app/guest-versions-pip.txt || true'
|
||
--run-command 'ls /usr/local/share/ms-playwright/ > /app/guest-versions-chromium.txt 2>/dev/null || true')
|
||
fi
|
||
# B-21 收尾:清掉两轮 apt 的缓存与 lists(--with-deps 装完后
|
||
# 不再有 apt 活动),把空间还给镜像。
|
||
VC_ARGS+=(
|
||
--run-command 'apt-get clean && rm -rf /var/lib/apt/lists/*')
|
||
run virt-customize "${VC_ARGS[@]}"
|
||
# B-18:virt-get-kernel 的 -o 是输出目录(不是文件),它在
|
||
# 目录里下载 vmlinuz-<ver> 与 initrd.img-<ver>;旧写法
|
||
# -o "$OUT/vmlinux" 会建出 vmlinux 目录,后面的 test -s 文件
|
||
# 校验必然失败。改成 -o "$OUT",再把 vmlinuz-* 移为 vmlinux
|
||
# 文件;initrd.img-* 留在 OUT 里作配套产物。另:第三轮真机
|
||
# 构建里 virt-get-kernel 首跑报过一次 "file receive cancelled
|
||
# by daemon",同一盘手动重跑即成功(瞬时故障),此处失败重跑
|
||
# 一次,与 B-05/B-06 的重试口径一致。
|
||
if [ "$DRY_RUN" = "1" ]; then
|
||
echo "DRY-RUN: virt-get-kernel -a $OUT/disk.raw -o $OUT"
|
||
echo "DRY-RUN: mv $OUT/vmlinuz-* $OUT/vmlinux"
|
||
else
|
||
run virt-get-kernel -a "$OUT/disk.raw" -o "$OUT" \
|
||
|| run virt-get-kernel -a "$OUT/disk.raw" -o "$OUT"
|
||
mv "$OUT"/vmlinuz-* "$OUT/vmlinux"
|
||
fi
|
||
# feat-chv-secfix(M4):构建时把客机内实际装好的版本
|
||
# 读回 BUILDINFO.versions(dpkg + pip freeze),名字列表
|
||
# 之外再有逐项版本——可审计、可复现比对。事前 lockfile
|
||
# 钉死(apt/pip 精确版本预先锁定)仍待构建宿主生成,
|
||
# 见 README 与就绪清单 §11;本步先保证“建成什么版本
|
||
# 都有记录”,不再只有名字。
|
||
if [ "$DRY_RUN" = "1" ]; then
|
||
echo "DRY-RUN: virt-cat -a $OUT/disk.raw /app/guest-versions-dpkg.txt > $OUT/guest-versions-dpkg.txt"
|
||
echo "DRY-RUN: virt-cat -a $OUT/disk.raw /app/guest-versions-pip.txt > $OUT/guest-versions-pip.txt"
|
||
echo "DRY-RUN: virt-cat -a $OUT/disk.raw /app/guest-versions-gt.txt > $OUT/guest-versions-gt.txt"
|
||
if [ "${PLAYWRIGHT:-1}" = "1" ]; then
|
||
echo "DRY-RUN: virt-cat -a $OUT/disk.raw /app/guest-versions-chromium.txt > $OUT/guest-versions-chromium.txt"
|
||
fi
|
||
else
|
||
virt-cat -a "$OUT/disk.raw" /app/guest-versions-dpkg.txt > "$OUT/guest-versions-dpkg.txt"
|
||
virt-cat -a "$OUT/disk.raw" /app/guest-versions-pip.txt > "$OUT/guest-versions-pip.txt"
|
||
virt-cat -a "$OUT/disk.raw" /app/guest-versions-gt.txt > "$OUT/guest-versions-gt.txt" || true
|
||
if [ "${PLAYWRIGHT:-1}" = "1" ]; then
|
||
virt-cat -a "$OUT/disk.raw" /app/guest-versions-chromium.txt > "$OUT/guest-versions-chromium.txt" || true
|
||
fi
|
||
fi
|
||
# 产物校验(干跑只打印,不执行校验命令的副作用部分)。
|
||
if [ "$DRY_RUN" = "1" ]; then
|
||
echo "DRY-RUN: test -s $OUT/vmlinux && test -s $OUT/disk.raw"
|
||
echo "DRY-RUN: python3 $SCRIPT_DIR/buildinfo.py $OUT $TAG $UBUNTU_VERSION $CODEX_VERSION $REPO_COMMIT <sha256> ${PLAYWRIGHT:-1} ${CHROMIUM_HEADLESS_SHELL_REV:-} # 生成 $OUT/BUILDINFO.json 并校验 tag=$TAG"
|
||
else
|
||
test -s "$OUT/vmlinux" || { echo "内核产物为空:$OUT/vmlinux" >&2; exit 1; }
|
||
test -s "$OUT/disk.raw" || { echo "磁盘产物为空:$OUT/disk.raw" >&2; exit 1; }
|
||
if [ "${PLAYWRIGHT:-1}" = "1" ] && [ -z "$CHROMIUM_HEADLESS_SHELL_REV" ]; then
|
||
echo "警告:CHROMIUM_HEADLESS_SHELL_REV 未设,Chromium 版本只记录、不钉死(V-9 验收须设该变量重建)" >&2
|
||
fi
|
||
python3 "$SCRIPT_DIR/buildinfo.py" "$OUT" "$TAG" "$UBUNTU_VERSION" "$CODEX_VERSION" "$REPO_COMMIT" "$UBUNTU_IMAGE_SHA256" "${PLAYWRIGHT:-1}" "${CHROMIUM_HEADLESS_SHELL_REV:-}"
|
||
fi
|
||
echo "tag=$TAG"
|
||
echo "out=$OUT"
|
||
if [ "$DRY_RUN" = "1" ]; then
|
||
echo "干跑结束:未执行任何构建步骤,镜像未建成"
|
||
else
|
||
echo "构建完成:$OUT/vmlinux $OUT/disk.raw $OUT/BUILDINFO.json"
|
||
fi
|