diff --git a/scripts/deepflow/df-web-ai-push-all.sh b/scripts/deepflow/df-web-ai-push-all.sh new file mode 100644 index 0000000..8889bdc --- /dev/null +++ b/scripts/deepflow/df-web-ai-push-all.sh @@ -0,0 +1,83 @@ +#!/bin/bash +# df-web-ai-push-all.sh +# 从 -.multi.tar (OCI) 逐个 load → retag → push 到目标仓库(multi-arch) +set -euo pipefail + +LOCAL_REG="${LOCAL_REG:-sealos.hub:5000}" +NERDCTL_BIN="${NERDCTL_BIN:-nerdctl}" +NERDCTL_NS="${NERDCTL_NS:-}" # 如 "k8s.io" +NC="${NERDCTL_BIN} ${NERDCTL_NS:+-n ${NERDCTL_NS}}" + +usage() { + cat < + LOCAL_REG=sealos.hub:5000 $0 + +说明: + - 对每个 tar: + 1) 解析 index.json,取 org.opencontainers.image.ref.name 作为源引用名(SRC_REF) + 2) nerdctl load -i + 3) 将 SRC_REF 重打为 \${LOCAL_REG}/ + 4) nerdctl push \${LOCAL_REG}/ # multi-arch 一次性推送 +EOF +} + +if [[ "${1:-}" =~ ^-h|--help$ ]]; then usage; exit 0; fi + +${NC} login "${LOCAL_REG}" || true + +shopt -s nullglob +TARS=(*.multi.tar) +shopt -u nullglob +[[ ${#TARS[@]} -gt 0 ]] || { echo "⚠️ 未找到 *.multi.tar"; exit 0; } + +get_src_ref_from_tar() { + # 从 OCI index.json 提取 ref.name;优先 manifest 注解,其次 index 注解 + local tar="$1" + local ref="" + ref=$(tar -xOf "$tar" index.json 2>/dev/null | \ + jq -r ' + .manifests[0].annotations["org.opencontainers.image.ref.name"] + // .annotations["org.opencontainers.image.ref.name"] + // empty + ') + echo -n "$ref" +} + +for TAR in "${TARS[@]}"; do + echo + echo "==> Processing $TAR" + + SRC_REF="$(get_src_ref_from_tar "$TAR")" + if [[ -z "$SRC_REF" ]]; then + # 回退:用文件名 -.multi.tar 推导 name:tag + BASE="$(basename "$TAR" .multi.tar)" + if [[ "$BASE" != *:* ]]; then + echo "❌ 无法从 $TAR 提取 SRC_REF,且文件名不含 格式。请改名或使用包含 ref.name 的归档。" + exit 2 + fi + SRC_REF="$BASE" + echo "ℹ️ 未找到 ref.name,使用文件名推导的源引用:$SRC_REF" + else + echo "SRC_REF: $SRC_REF" + fi + + NAME_TAG="${SRC_REF##*/}" # 仅保留 + DEST="${LOCAL_REG}/${NAME_TAG}" + echo "DEST: $DEST" + + echo "==> Load $TAR" + ${NC} load -i "$TAR" + + echo "==> Tag $SRC_REF -> $DEST" + ${NC} tag "$SRC_REF" "$DEST" + + echo "==> Push $DEST (multi-arch)" + ${NC} push "$DEST" + + echo "✅ DONE: $DEST" +done + +echo +echo "All done. (multi-arch push)" diff --git a/scripts/deepflow/pull_save_scp_image_arm64.sh b/scripts/deepflow/pull_save_scp_image_arm64.sh deleted file mode 100644 index 8b32110..0000000 --- a/scripts/deepflow/pull_save_scp_image_arm64.sh +++ /dev/null @@ -1,198 +0,0 @@ -#!/bin/bash -# deepflow/pull_save_scp_image_arm64.sh -# 目标:即使远端是 x86,也只拉取/保存 arm64 变体,并强校验;支持批量与 --rm-remote 清理 -set -euo pipefail - -REMOTE_HOST="${REMOTE_HOST:-root@10.1.3.179}" -DEST_DIR="${DEST_DIR:-$HOME/Desktop}" -RM_REMOTE=0 - -usage() { - cat < [image2 ...] [--rm-remote] - $0 -f images.txt [--rm-remote] - -说明: - - 支持批量处理: - • 多个参数: ./pull_save_scp_image_arm64.sh image1 image2 ... - • 文件清单: ./pull_save_scp_image_arm64.sh -f images.txt - (清单支持 # 注释与空行) - - - 只拉 arm64 & save arm64: - • docker pull --platform=linux/arm64 - • docker image inspect --format '{{.Architecture}}' 二次确认 - • 以镜像ID保存,避免 tag→manifest list 在 x86 上回退到 amd64 - - - 保存后校验: - • 在远端解析 tar 的 manifest.json 和对应 config - • 逐个检查 "architecture":"arm64",确保 tar 内确实是 arm64 - - - 可配置环境变量: - • REMOTE_HOST (默认 root@10.1.3.179) - • DEST_DIR (默认 ~/Desktop) - - - 额外选项: - • --rm-remote 成功拷贝到本地后自动删除远端 /tmp/*.tar; - 任一步失败也会自动清理远端临时文件,避免残留。 - -示例: - $0 dfcloud-image-registry-vpc.cn-beijing.cr.aliyuncs.com/dev/df-web-ai:v6.6.18839 - $0 -f images.txt --rm-remote -EOF -} - -# -------- 参数解析 -------- -IMAGES=() -LIST_FILE="" -if [[ $# -eq 0 ]]; then usage; exit 1; fi - -ARGS=() -while [[ $# -gt 0 ]]; do - case "$1" in - -h|--help) usage; exit 0 ;; - --rm-remote) RM_REMOTE=1; shift ;; - -f) - [[ $# -ge 2 ]] || { echo "❌ 缺少镜像清单文件"; exit 1; } - LIST_FILE="$2"; shift 2 ;; - *) - ARGS+=("$1"); shift ;; - esac -done - -if [[ -n "$LIST_FILE" ]]; then - [[ -f "$LIST_FILE" ]] || { echo "❌ 文件不存在: $LIST_FILE"; exit 1; } - while IFS= read -r line; do - line="${line%%#*}" - line="$(echo -n "$line" | xargs || true)" - [[ -n "$line" ]] || continue - IMAGES+=("$line") - done < "$LIST_FILE" -fi - -if [[ ${#ARGS[@]} -gt 0 ]]; then - IMAGES+=("${ARGS[@]}") -fi - -[[ ${#IMAGES[@]} -gt 0 ]] || { echo "❌ 没有可处理的镜像"; exit 1; } - -echo "🖥️ 远端: $REMOTE_HOST" -echo "💾 本地保存目录: $DEST_DIR" -echo "🧹 rm-remote: $([[ $RM_REMOTE -eq 1 ]] && echo ON || echo OFF)" -mkdir -p "$DEST_DIR" - -# -------- 远端校验脚本内容(用 cat heredoc 赋值,兼容 macOS 老 bash) -------- -REMOTE_VERIFY_PY="$(cat <<'PYCODE' -import sys, tarfile, json -tar_path = sys.argv[1] -with tarfile.open(tar_path, "r") as tf: - manifest = json.load(tf.extractfile("manifest.json")) - for item in manifest: - cfg = item.get("Config") - if not cfg: - print("NO_CONFIG_IN_MANIFEST", file=sys.stderr); sys.exit(2) - f = tf.extractfile(cfg) - if f is None: - print("CONFIG_NOT_FOUND", file=sys.stderr); sys.exit(3) - cfg_json = json.load(f) - arch = cfg_json.get("architecture") - if arch != "arm64": - print(f"BAD_ARCH:{arch}", file=sys.stderr); sys.exit(4) -print("OK") -PYCODE -)" - -# 为了安全传输到远端,这里将 Python 内容做一次 printf %q 转义 -#(避免某些 shell/ssh 环境下的字符解释问题) -escape_for_ssh() { - printf "%s" "$1" | python3 - <<'P' -import sys, shlex -data=sys.stdin.read() -print(shlex.quote(data)) -P -} - -REMOTE_VERIFY_PY_Q=$(escape_for_ssh "$REMOTE_VERIFY_PY") - -# -------- 处理单个镜像 -------- -process_image() { - local IMAGE="$1" - - local NAME_TAG="${IMAGE##*/}" # e.g. weaviate:1.30.0 - local NAME="${NAME_TAG%%:*}" # weaviate - local TAG="${NAME_TAG##*:}" # 1.30.0 - if [[ "$NAME" == "$NAME_TAG" ]]; then TAG="latest"; fi - - local FILE_NAME="${NAME}-${TAG}.arm64.tar" - local REMOTE_TAR="/tmp/${FILE_NAME}" - local DEST_PATH="${DEST_DIR}/${FILE_NAME}" - - echo - echo "==============================" - echo "📦 镜像: $IMAGE" - echo "🎯 仅拉取平台: linux/arm64" - echo "📁 导出文件名: $FILE_NAME" - echo "==============================" - - # 失败即清理远端临时文件 - local CLEAN_ON_FAILURE=1 - trap 'if [[ "${CLEAN_ON_FAILURE:-0}" -eq 1 ]]; then ssh -o BatchMode=yes "'"$REMOTE_HOST"'" "rm -f \"'"$REMOTE_TAR"'\"" || true; fi' RETURN - - # 1) 强制拉 arm64 - echo "🚀 远端拉取镜像..." - ssh -o BatchMode=yes "$REMOTE_HOST" "docker pull --platform=linux/arm64 \"$IMAGE\"" - - # 2) 获取 arm64 变体镜像ID - echo "🔎 提取 arm64 镜像ID..." - local IMAGE_ID - IMAGE_ID="$(ssh "$REMOTE_HOST" " - docker image inspect \"$IMAGE\" \ - --format '{{.Id}} {{.Architecture}}' 2>/dev/null \ - | awk '\$2==\"arm64\"{print \$1; exit}' - ")" - if [[ -z "${IMAGE_ID:-}" ]]; then - echo "❌ 未找到 arm64 变体镜像ID,可能仓库不包含 arm64。"; return 12 - fi - echo "✅ arm64 镜像ID: $IMAGE_ID" - - # 3) 二次确认该 ID 的架构为 arm64 - echo "🧪 inspect 架构确认..." - ssh "$REMOTE_HOST" " - arch=\$(docker image inspect --format '{{.Architecture}}' $IMAGE_ID | head -n1); \ - if [[ \"\$arch\" != \"arm64\" ]]; then - echo '❌ 镜像ID架构校验失败: '\"\$arch\"; exit 13; fi; \ - echo '✅ 架构: '\"\$arch\" - " - - # 4) 以镜像ID保存 - echo "💾 保存为: $REMOTE_TAR ..." - ssh "$REMOTE_HOST" "docker save $IMAGE_ID > \"$REMOTE_TAR\"" - - # 5) 解包校验 tar 内架构 - echo "🧬 校验 tar 包内部 architecture..." - ssh "$REMOTE_HOST" "python3 -c $REMOTE_VERIFY_PY_Q \"$REMOTE_TAR\"" - - # 6) 拷回本地 - echo "📥 拷贝到本地: $DEST_PATH ..." - scp "$REMOTE_HOST:$REMOTE_TAR" "$DEST_PATH" - - # 7) 可选删除远端临时 tar;关闭失败清理 trap - if [[ $RM_REMOTE -eq 1 ]]; then - echo "🧹 删除远端临时文件: $REMOTE_TAR" - ssh "$REMOTE_HOST" "rm -f \"$REMOTE_TAR\"" - else - echo "ℹ️ 远端临时文件保留: $REMOTE_TAR" - fi - - CLEAN_ON_FAILURE=0 - trap - RETURN - echo "✅ 完成: $DEST_PATH (arm64 only)" -} - -# -------- 批量执行 -------- -for img in "${IMAGES[@]}"; do - process_image "$img" -done - -echo -echo "🎉 所有任务完成。" diff --git a/scripts/deepflow/pull_save_scp_image_multi_arch.sh b/scripts/deepflow/pull_save_scp_image_multi_arch.sh new file mode 100644 index 0000000..94e19e7 --- /dev/null +++ b/scripts/deepflow/pull_save_scp_image_multi_arch.sh @@ -0,0 +1,181 @@ +#!/usr/bin/env bash +# deepflow/pull_save_scp_image_multi_arch.sh +# 远端:multi-arch pull(优先 --all-platforms,回退逐平台) +# -> image convert (--oci --all-platforms) 到临时本地引用 +# -> save -o /tmp/-.multi.tar (docker-archive) +# -> scp 回本地 +set -euo pipefail + +REMOTE_HOST="${REMOTE_HOST:-root@10.1.3.179}" +DEST_DIR="${DEST_DIR:-$HOME/Desktop}" +REMOTE_TMPDIR="${REMOTE_TMPDIR:-/tmp}" +RM_REMOTE="${RM_REMOTE:-0}" + +REMOTE_NERDCTL="${REMOTE_NERDCTL:-nerdctl}" +REMOTE_NERDCTL_NS="${REMOTE_NERDCTL_NS:-}" # 例如 "k8s.io" +REMOTE_NC="${REMOTE_NERDCTL} ${REMOTE_NERDCTL_NS:+-n ${REMOTE_NERDCTL_NS}}" + +PLATFORMS_DEFAULT="linux/amd64,linux/arm64" +PLATFORMS="${PLATFORMS:-$PLATFORMS_DEFAULT}" + +usage() { + cat < [image2 ...] [--rm-remote] + $0 -f images.txt [--rm-remote] + +流程(远端): + 1) ${REMOTE_NC} pull --all-platforms # 不支持则逐平台 --platform + 2) ${REMOTE_NC} image convert --oci --all-platforms + 3) ${REMOTE_NC} save -o ${REMOTE_TMPDIR}/-.multi.tar + 4) scp 回本地 ${DEST_DIR} + +环境变量: + REMOTE_HOST, DEST_DIR, REMOTE_TMPDIR, REMOTE_NERDCTL, REMOTE_NERDCTL_NS, PLATFORMS, RM_REMOTE +EOF +} + +# ---------- 参数解析 ---------- +IMAGES=() +LIST_FILE="" +if [[ $# -eq 0 ]]; then usage; exit 1; fi + +ARGS=() +while [[ $# -gt 0 ]]; do + case "$1" in + -h|--help) usage; exit 0 ;; + --rm-remote) RM_REMOTE=1; shift ;; + -f) + [[ $# -ge 2 ]] || { echo "❌ 缺少镜像清单文件"; exit 1; } + LIST_FILE="$2"; shift 2 ;; + *) ARGS+=("$1"); shift ;; + esac +done + +if [[ -n "$LIST_FILE" ]]; then + [[ -f "$LIST_FILE" ]] || { echo "❌ 文件不存在: $LIST_FILE"; exit 1; } + while IFS= read -r line; do + line="${line%%#*}"; line="$(echo -n "$line" | xargs || true)" + [[ -n "$line" ]] || continue + IMAGES+=("$line") + done < "$LIST_FILE" +fi +if [[ ${#ARGS[@]} -gt 0 ]]; then IMAGES+=("${ARGS[@]}"); fi +[[ ${#IMAGES[@]} -gt 0 ]] || { echo "❌ 没有可处理的镜像"; exit 1; } + +echo "🖥️ 远端: $REMOTE_HOST" +echo "📂 本地目录: $DEST_DIR" +echo "🧭 命名空间: ${REMOTE_NERDCTL_NS:-}" +echo "🧹 rm-remote: $([[ $RM_REMOTE -eq 1 ]] && echo ON || echo OFF)" +echo "🧩 回退平台: $PLATFORMS" +mkdir -p "$DEST_DIR" + +# ---------- 预检查 ---------- +ssh -o BatchMode=yes "$REMOTE_HOST" "command -v ${REMOTE_NERDCTL} >/dev/null" \ + || { echo "❌ 远端未安装 ${REMOTE_NERDCTL}"; exit 1; } +ssh -o BatchMode=yes "$REMOTE_HOST" "test -d ${REMOTE_TMPDIR}" \ + || { echo "❌ 远端临时目录不存在: ${REMOTE_TMPDIR}"; exit 1; } + +REMOTE_SUPPORTS_ALL_PLATFORMS=0 +if ssh -o BatchMode=yes "$REMOTE_HOST" "${REMOTE_NC} pull --help 2>/dev/null | grep -q -- '--all-platforms'"; then + REMOTE_SUPPORTS_ALL_PLATFORMS=1 +fi + +# ---------- 工具函数 ---------- +rand_suffix() { LC_ALL=C tr -dc a-z0-9 /dev/null 2>&1 || true + fi' RETURN + + # 1) 拉取多架构 + if [[ $REMOTE_SUPPORTS_ALL_PLATFORMS -eq 1 ]]; then + ssh -o BatchMode=yes "$REMOTE_HOST" \ + "set -euo pipefail; ${REMOTE_NC} pull --all-platforms $Q_IMAGE" + else + echo "ℹ️ 远端不支持 --all-platforms,逐平台拉取: $PLATFORMS" + IFS=, read -r -a arr <<< "$PLATFORMS" + for p in "${arr[@]}"; do + local QP; QP=$(printf %q "$p") + ssh -o BatchMode=yes "$REMOTE_HOST" \ + "set -euo pipefail; ${REMOTE_NC} pull --platform=$QP $Q_IMAGE" + done + fi + + # 2) 转为 OCI(到临时本地引用),确保包含所有平台 + ssh -o BatchMode=yes "$REMOTE_HOST" \ + "set -euo pipefail; ${REMOTE_NC} image convert --oci --all-platforms $Q_IMAGE $Q_TARGET" + + # 3) 保存为 docker-archive TAR + ssh -o BatchMode=yes "$REMOTE_HOST" \ + "set -euo pipefail; ${REMOTE_NC} save -o $Q_TAR $Q_TARGET" + + # 4) 回传 + scp -q "$REMOTE_HOST:$REMOTE_TAR" "$DEST_PATH" + + # 5) 清理 + if [[ $RM_REMOTE -eq 1 ]]; then + ssh -o BatchMode=yes "$REMOTE_HOST" "rm -f $Q_TAR" + fi + ssh -o BatchMode=yes "$REMOTE_HOST" "${REMOTE_NC} rmi -f $Q_TARGET" >/dev/null 2>&1 || true + + CLEAN_ON_FAILURE=0 + trap - RETURN + echo "✅ OK: $DEST_PATH (docker-archive, multi-arch)" + echo " 加载:nerdctl load -i \"$DEST_PATH\"" + echo " 基本校验:tar tf \"$DEST_PATH\" | egrep 'manifest.json|repositories' | sed -n '1,5p'" + echo " 平台确认(加载后):nerdctl image inspect \"$TARGET_REF\" --mode=native | jq '.[0].Manifest.Manifests[].Platform'" +} + +for img in "${IMAGES[@]}"; do + process_image "$img" +done + +echo +echo "🎉 全部 multi-arch 导出完成。" diff --git a/scripts/deepflow/pull_save_scp_image_x86.sh b/scripts/deepflow/pull_save_scp_image_x86.sh deleted file mode 100644 index 0164f5a..0000000 --- a/scripts/deepflow/pull_save_scp_image_x86.sh +++ /dev/null @@ -1,196 +0,0 @@ -#!/bin/bash -# deepflow/pull_save_scp_image_amd64.sh -# 目标:仅拉取/保存 amd64 变体,并强校验;支持批量与 --rm-remote 清理 -set -euo pipefail - -REMOTE_HOST="${REMOTE_HOST:-root@10.1.3.179}" -DEST_DIR="${DEST_DIR:-$HOME/Desktop}" -RM_REMOTE=0 - -usage() { - cat < [image2 ...] [--rm-remote] - $0 -f images.txt [--rm-remote] - -说明: - - 支持批量处理: - • 多个参数: ./pull_save_scp_image_amd64.sh image1 image2 ... - • 文件清单: ./pull_save_scp_image_amd64.sh -f images.txt - (清单支持 # 注释与空行) - - - 只拉 amd64 & save amd64: - • docker pull --platform=linux/amd64 - • docker image inspect --format '{{.Architecture}}' 二次确认 - • 以镜像ID保存,避免 tag→manifest list 在异构主机上回退到其他架构 - - - 保存后校验: - • 在远端解析 tar 的 manifest.json 和对应 config - • 逐个检查 "architecture":"amd64",确保 tar 内确实是 amd64 - - - 可配置环境变量: - • REMOTE_HOST (默认 root@10.1.3.179) - • DEST_DIR (默认 ~/Desktop) - - - 额外选项: - • --rm-remote 成功拷贝到本地后自动删除远端 /tmp/*.tar; - 任一步失败也会自动清理远端临时文件,避免残留。 - -示例: - $0 dfcloud-image-registry-vpc.cn-beijing.cr.aliyuncs.com/dev/df-web-ai:v6.6.18839 - $0 -f images.txt --rm-remote -EOF -} - -# -------- 参数解析 -------- -IMAGES=() -LIST_FILE="" -if [[ $# -eq 0 ]]; then usage; exit 1; fi - -ARGS=() -while [[ $# -gt 0 ]]; do - case "$1" in - -h|--help) usage; exit 0 ;; - --rm-remote) RM_REMOTE=1; shift ;; - -f) - [[ $# -ge 2 ]] || { echo "❌ 缺少镜像清单文件"; exit 1; } - LIST_FILE="$2"; shift 2 ;; - *) - ARGS+=("$1"); shift ;; - esac -done - -if [[ -n "$LIST_FILE" ]]; then - [[ -f "$LIST_FILE" ]] || { echo "❌ 文件不存在: $LIST_FILE"; exit 1; } - while IFS= read -r line; do - line="${line%%#*}" - line="$(echo -n "$line" | xargs || true)" - [[ -n "$line" ]] || continue - IMAGES+=("$line") - done < "$LIST_FILE" -fi - -if [[ ${#ARGS[@]} -gt 0 ]]; then - IMAGES+=("${ARGS[@]}") -fi - -[[ ${#IMAGES[@]} -gt 0 ]] || { echo "❌ 没有可处理的镜像"; exit 1; } - -echo "🖥️ 远端: $REMOTE_HOST" -echo "💾 本地保存目录: $DEST_DIR" -echo "🧹 rm-remote: $([[ $RM_REMOTE -eq 1 ]] && echo ON || echo OFF)" -mkdir -p "$DEST_DIR" - -# -------- 远端校验脚本内容(用 cat heredoc 赋值,兼容 macOS 老 bash) -------- -REMOTE_VERIFY_PY="$(cat <<'PYCODE' -import sys, tarfile, json -tar_path = sys.argv[1] -with tarfile.open(tar_path, "r") as tf: - manifest = json.load(tf.extractfile("manifest.json")) - for item in manifest: - cfg = item.get("Config") - if not cfg: - print("NO_CONFIG_IN_MANIFEST", file=sys.stderr); sys.exit(2) - f = tf.extractfile(cfg) - if f is None: - print("CONFIG_NOT_FOUND", file=sys.stderr); sys.exit(3) - cfg_json = json.load(f) - arch = cfg_json.get("architecture") - if arch != "amd64": - print(f"BAD_ARCH:{arch}", file=sys.stderr); sys.exit(4) -print("OK") -PYCODE -)" - -escape_for_ssh() { - printf "%s" "$1" | python3 - <<'P' -import sys, shlex -data=sys.stdin.read() -print(shlex.quote(data)) -P -} -REMOTE_VERIFY_PY_Q=$(escape_for_ssh "$REMOTE_VERIFY_PY") - -# -------- 处理单个镜像 -------- -process_image() { - local IMAGE="$1" - - local NAME_TAG="${IMAGE##*/}" # e.g. weaviate:1.30.0 - local NAME="${NAME_TAG%%:*}" # weaviate - local TAG="${NAME_TAG##*:}" # 1.30.0 - if [[ "$NAME" == "$NAME_TAG" ]]; then TAG="latest"; fi - - local FILE_NAME="${NAME}-${TAG}.amd64.tar" - local REMOTE_TAR="/tmp/${FILE_NAME}" - local DEST_PATH="${DEST_DIR}/${FILE_NAME}" - - echo - echo "==============================" - echo "📦 镜像: $IMAGE" - echo "🎯 仅拉取平台: linux/amd64" - echo "📁 导出文件名: $FILE_NAME" - echo "==============================" - - # 失败即清理远端临时文件 - local CLEAN_ON_FAILURE=1 - trap 'if [[ "${CLEAN_ON_FAILURE:-0}" -eq 1 ]]; then ssh -o BatchMode=yes "'"$REMOTE_HOST"'" "rm -f \"'"$REMOTE_TAR"'\"" || true; fi' RETURN - - # 1) 强制拉 amd64 - echo "🚀 远端拉取镜像..." - ssh -o BatchMode=yes "$REMOTE_HOST" "docker pull --platform=linux/amd64 \"$IMAGE\"" - - # 2) 获取 amd64 变体镜像ID - echo "🔎 提取 amd64 镜像ID..." - local IMAGE_ID - IMAGE_ID="$(ssh "$REMOTE_HOST" " - docker image inspect \"$IMAGE\" \ - --format '{{.Id}} {{.Architecture}}' 2>/dev/null \ - | awk '\$2==\"amd64\"{print \$1; exit}' - ")" - if [[ -z "${IMAGE_ID:-}" ]]; then - echo "❌ 未找到 amd64 变体镜像ID,可能仓库不包含 amd64。"; return 12 - fi - echo "✅ amd64 镜像ID: $IMAGE_ID" - - # 3) 二次确认该 ID 的架构为 amd64 - echo "🧪 inspect 架构确认..." - ssh "$REMOTE_HOST" " - arch=\$(docker image inspect --format '{{.Architecture}}' $IMAGE_ID | head -n1); \ - if [[ \"\$arch\" != \"amd64\" ]]; then - echo '❌ 镜像ID架构校验失败: '\"\$arch\"; exit 13; fi; \ - echo '✅ 架构: '\"\$arch\" - " - - # 4) 以镜像ID保存 - echo "💾 保存为: $REMOTE_TAR ..." - ssh "$REMOTE_HOST" "docker save $IMAGE_ID > \"$REMOTE_TAR\"" - - # 5) 解包校验 tar 内架构 - echo "🧬 校验 tar 包内部 architecture..." - ssh "$REMOTE_HOST" "python3 -c $REMOTE_VERIFY_PY_Q \"$REMOTE_TAR\"" - - # 6) 拷回本地 - echo "📥 拷贝到本地: $DEST_PATH ..." - scp "$REMOTE_HOST:$REMOTE_TAR" "$DEST_PATH" - - # 7) 可选删除远端临时 tar;关闭失败清理 trap - if [[ $RM_REMOTE -eq 1 ]]; then - echo "🧹 删除远端临时文件: $REMOTE_TAR" - ssh "$REMOTE_HOST" "rm -f \"$REMOTE_TAR\"" - else - echo "ℹ️ 远端临时文件保留: $REMOTE_TAR" - fi - - CLEAN_ON_FAILURE=0 - trap - RETURN - echo "✅ 完成: $DEST_PATH (amd64 only)" -} - -# -------- 批量执行 -------- -for img in "${IMAGES[@]}"; do - process_image "$img" -done - -echo -echo "🎉 所有任务完成。" -