update scripts/deepflow/pull_save_scp_image_multi_arch.sh
This commit is contained in:
parent
ff109b3085
commit
9320acf1a0
83
scripts/deepflow/df-web-ai-push-all.sh
Normal file
83
scripts/deepflow/df-web-ai-push-all.sh
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# df-web-ai-push-all.sh
|
||||||
|
# 从 <name>-<tag>.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 <<EOF
|
||||||
|
用法:
|
||||||
|
# 扫描当前目录 *.multi.tar 全部推送到 \${LOCAL_REG}/<name:tag>
|
||||||
|
LOCAL_REG=sealos.hub:5000 $0
|
||||||
|
|
||||||
|
说明:
|
||||||
|
- 对每个 tar:
|
||||||
|
1) 解析 index.json,取 org.opencontainers.image.ref.name 作为源引用名(SRC_REF)
|
||||||
|
2) nerdctl load -i <tar>
|
||||||
|
3) 将 SRC_REF 重打为 \${LOCAL_REG}/<name:tag>
|
||||||
|
4) nerdctl push \${LOCAL_REG}/<name:tag> # 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
|
||||||
|
# 回退:用文件名 <name>-<tag>.multi.tar 推导 name:tag
|
||||||
|
BASE="$(basename "$TAR" .multi.tar)"
|
||||||
|
if [[ "$BASE" != *:* ]]; then
|
||||||
|
echo "❌ 无法从 $TAR 提取 SRC_REF,且文件名不含 <name:tag> 格式。请改名或使用包含 ref.name 的归档。"
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
SRC_REF="$BASE"
|
||||||
|
echo "ℹ️ 未找到 ref.name,使用文件名推导的源引用:$SRC_REF"
|
||||||
|
else
|
||||||
|
echo "SRC_REF: $SRC_REF"
|
||||||
|
fi
|
||||||
|
|
||||||
|
NAME_TAG="${SRC_REF##*/}" # 仅保留 <name:tag>
|
||||||
|
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)"
|
||||||
@ -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 <<EOF
|
|
||||||
用法:
|
|
||||||
$0 <image1> [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 "🎉 所有任务完成。"
|
|
||||||
181
scripts/deepflow/pull_save_scp_image_multi_arch.sh
Normal file
181
scripts/deepflow/pull_save_scp_image_multi_arch.sh
Normal file
@ -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/<name>-<tag>.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 <<EOF
|
||||||
|
用法:
|
||||||
|
$0 <image1> [image2 ...] [--rm-remote]
|
||||||
|
$0 -f images.txt [--rm-remote]
|
||||||
|
|
||||||
|
流程(远端):
|
||||||
|
1) ${REMOTE_NC} pull --all-platforms <IMAGE> # 不支持则逐平台 --platform
|
||||||
|
2) ${REMOTE_NC} image convert --oci --all-platforms <IMAGE> <TARGET_REF>
|
||||||
|
3) ${REMOTE_NC} save -o ${REMOTE_TMPDIR}/<name>-<tag>.multi.tar <TARGET_REF>
|
||||||
|
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:-<default>}"
|
||||||
|
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/urandom | head -c 6; }
|
||||||
|
|
||||||
|
mk_target_ref() {
|
||||||
|
local image="$1"
|
||||||
|
local repo="${image%%[@:]*}"
|
||||||
|
local suffix="${image#${repo}}"
|
||||||
|
local tag="latest"
|
||||||
|
if [[ "$suffix" == :* ]]; then
|
||||||
|
tag="${suffix#:}"
|
||||||
|
elif [[ "$suffix" == @* ]]; then
|
||||||
|
tag="digest-$(echo "${suffix#@}" | cut -c1-12)"
|
||||||
|
fi
|
||||||
|
echo "${repo}:${tag}-oci-$(rand_suffix)"
|
||||||
|
}
|
||||||
|
|
||||||
|
process_image() {
|
||||||
|
local IMAGE="$1"
|
||||||
|
|
||||||
|
local NAME_TAG="${IMAGE##*/}"
|
||||||
|
local NAME="${NAME_TAG%%[:@]*}"
|
||||||
|
local TAG_OR_DIGEST="${NAME_TAG#${NAME}}"
|
||||||
|
local TAG="latest"
|
||||||
|
if [[ "$TAG_OR_DIGEST" == :* ]]; then
|
||||||
|
TAG="${TAG_OR_DIGEST#:}"
|
||||||
|
elif [[ "$TAG_OR_DIGEST" == @* ]]; then
|
||||||
|
TAG="digest-$(echo "${TAG_OR_DIGEST#@}" | cut -c1-12)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
local TARGET_REF; TARGET_REF="$(mk_target_ref "$IMAGE")"
|
||||||
|
local REMOTE_TAR="${REMOTE_TMPDIR}/${NAME}-${TAG}.multi.tar"
|
||||||
|
local DEST_PATH="${DEST_DIR}/${NAME}-${TAG}.multi.tar"
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "=============================="
|
||||||
|
echo "📦 IMAGE : $IMAGE"
|
||||||
|
echo "🎯 TARGET_REF : $TARGET_REF"
|
||||||
|
echo "📁 REMOTE_TAR : $REMOTE_TAR"
|
||||||
|
echo "=============================="
|
||||||
|
|
||||||
|
# 本地先把变量做 shell 安全转义,拼到远端命令里(避免引号问题)
|
||||||
|
local Q_IMAGE Q_TARGET Q_TAR
|
||||||
|
Q_IMAGE=$(printf %q "$IMAGE")
|
||||||
|
Q_TARGET=$(printf %q "$TARGET_REF")
|
||||||
|
Q_TAR=$(printf %q "$REMOTE_TAR")
|
||||||
|
|
||||||
|
# 失败清理
|
||||||
|
local CLEAN_ON_FAILURE=1
|
||||||
|
trap 'if [[ "${CLEAN_ON_FAILURE:-0}" -eq 1 ]]; then
|
||||||
|
ssh -o BatchMode=yes "'"$REMOTE_HOST"'" "rm -f '"$Q_TAR"'" || true
|
||||||
|
ssh -o BatchMode=yes "'"$REMOTE_HOST"'" "'"${REMOTE_NC}"' rmi -f '"$Q_TARGET"' >/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 导出完成。"
|
||||||
@ -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 <<EOF
|
|
||||||
用法:
|
|
||||||
$0 <image1> [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 "🎉 所有任务完成。"
|
|
||||||
|
|
||||||
Loading…
Reference in New Issue
Block a user