#!/usr/bin/env bash # 小白复盘唯一安全构建入口(HEL-235 固化) # 方式:从明确 Git 提交 git archive 流式传输到部署机 docker build,不使用任何服务器工作树。 # 铁律:禁止在服务器目录(如 /opt/1panel/docker/compose/xiaobaifupan)里 docker build; # 禁止构建 latest 等不带提交短号的 tag;严禁向 192.168.200.36 构建或部署。 set -euo pipefail HOST_DEFAULT="moxiaobai@192.168.200.11" REPO_NAME="xiaobai-review" usage() { cat <<'EOF' 用法: tools/build_image.sh 提交号(完整或前缀),必须能被 origin 解析;构建前会自动 fetch 镜像 tag,必须以 -<提交短号7位> 结尾,锁定镜像来源;禁止 latest、rollback-* 示例: tools/build_image.sh cefc86917d89 verify-hel235-cefc869 说明: 仅构建镜像,不启动、不替换任何容器;换版与回滚另行人工执行。 EOF exit 2 } [ $# -eq 2 ] || usage COMMIT="$1" TAG="$2" HOST="${XB_BUILD_HOST:-$HOST_DEFAULT}" case "$HOST" in *192.168.200.36*) echo "拒绝:192.168.200.36 已永久废弃,严禁在其上构建或部署。" >&2 exit 1 ;; esac cd "$(git rev-parse --show-toplevel)" echo "==> 同步远端引用" git fetch origin --prune --quiet FULL_SHA="$(git rev-parse --verify --quiet "${COMMIT}^{commit}" || true)" if [ -z "$FULL_SHA" ]; then echo "拒绝:提交 ${COMMIT} 无法解析。构建源必须锁定到已推送 origin 的明确提交。" >&2 exit 1 fi SHORT="${FULL_SHA:0:7}" SUBJECT="$(git log -1 --format=%s "$FULL_SHA")" case "$TAG" in latest) echo "拒绝:禁止构建 latest,模糊 tag 无法追溯来源提交。" >&2 exit 1 ;; rollback-*) echo "拒绝:rollback-* 是部署时对既有镜像的人工 docker tag,不允许用来构建。" >&2 exit 1 ;; esac if [[ "$TAG" != *-"$SHORT" ]]; then echo "拒绝:tag「${TAG}」必须以 -${SHORT} 结尾,保证镜像 tag 与来源提交一一对应。" >&2 exit 1 fi echo "==> 构建计划" echo " 提交: ${FULL_SHA} ${SUBJECT}" echo " 镜像: ${REPO_NAME}:${TAG} @ ${HOST}" echo " 方式: git archive 流式构建(不读取服务器上任何代码目录)" echo "==> 流式构建开始" git archive --format=tar "$FULL_SHA" \ | ssh -o BatchMode=yes "$HOST" docker build --rm \ -t "${REPO_NAME}:${TAG}" \ --label "org.opencontainers.image.revision=${FULL_SHA}" \ --label "org.opencontainers.image.created=$(date -u +%Y-%m-%dT%H:%M:%SZ)" \ --label "org.opencontainers.image.source=git-archive-stream" \ - echo "==> 回读校验镜像内记录的提交号" GOT="$(ssh -o BatchMode=yes "$HOST" "docker image inspect ${REPO_NAME}:${TAG} --format '{{index .Config.Labels \"org.opencontainers.image.revision\"}}'" 2>/dev/null || true)" if [ "$GOT" != "$FULL_SHA" ]; then echo "校验失败:镜像 revision='${GOT:-<空>}',期望 ${FULL_SHA}。删除不可信镜像,中止。" >&2 ssh -o BatchMode=yes "$HOST" docker rmi "${REPO_NAME}:${TAG}" >/dev/null 2>&1 || true exit 1 fi IMAGE_ID="$(ssh -o BatchMode=yes "$HOST" "docker image inspect ${REPO_NAME}:${TAG} --format '{{.Id}}'")" SHORT_ID="${IMAGE_ID##*:}" ssh -o BatchMode=yes "$HOST" \ "mkdir -p ~/xiaobai-build && printf '%s\t%s\t%s\t%s\tgit-archive-stream\n' \"\$(date '+%F %T')\" ${REPO_NAME}:${TAG} ${FULL_SHA} ${SHORT_ID} >> ~/xiaobai-build/BUILD_LOG.tsv" echo "==> 完成" echo " ${REPO_NAME}:${TAG} (${SHORT_ID})" echo " 来源提交 ${FULL_SHA} 已写入镜像 label 与 ~/xiaobai-build/BUILD_LOG.tsv"