Files
qiming/qimingclaw/scripts/download-sidecars.sh

377 lines
11 KiB
Bash
Raw Permalink 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
# 下载 sidecar 二进制到本地缓存目录(多平台)
#
# 支持 target:
# - x86_64-pc-windows-msvc
# - aarch64-pc-windows-msvc
# - x86_64-apple-darwin
# - aarch64-apple-darwin
# - x86_64-unknown-linux-gnu
# - aarch64-unknown-linux-gnu
#
# 用法:
# ./scripts/download-sidecars.sh # 自动从 GitHub 获取最新 mcp-proxy 版本
# ./scripts/download-sidecars.sh --target x86_64-pc-windows-msvc
# ./scripts/download-sidecars.sh --all-common
# ./scripts/download-sidecars.sh --all-common
# ./scripts/download-sidecars.sh --cache-dir .cache/sidecars
# ./scripts/download-sidecars.sh --mcp-version 0.1.48 # 指定 mcp-proxy 版本
# ./scripts/download-sidecars.sh --force # 强制重新下载
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
CACHE_DIR="$ROOT_DIR/.cache/sidecars"
TMP_DIR="/tmp/qiming-sidecar-download"
MCP_VERSION="0.1.53"
NODE_VERSION="22.14.0"
FORCE=0
DRY_RUN=0
ALL_COMMON=0
NO_CHECK=0
TARGETS=()
LAST_SUCCESS_URL=""
MCP_GITHUB_REPO="qiming-ai/mcp-proxy"
# 从 GitHub releases 获取最新版本号
get_latest_mcp_version() {
local api_url="https://api.github.com/repos/${MCP_GITHUB_REPO}/releases/latest"
local response
response=$(curl -fsSL "$api_url" 2>/dev/null) || {
echo "ERROR: 无法从 GitHub 获取最新版本" >&2
return 1
}
local tag_name
tag_name=$(echo "$response" | grep -m1 '"tag_name"' | sed 's/.*"tag_name"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/')
if [[ -z "$tag_name" ]]; then
echo "ERROR: 无法解析 GitHub response 中的 tag_name" >&2
return 1
fi
# 移除前缀 'v' (如 v0.1.48 -> 0.1.48)
echo "${tag_name#v}"
}
COMMON_TARGETS=(
"x86_64-pc-windows-msvc"
"aarch64-pc-windows-msvc"
"x86_64-apple-darwin"
"aarch64-apple-darwin"
"x86_64-unknown-linux-gnu"
"aarch64-unknown-linux-gnu"
)
while [[ $# -gt 0 ]]; do
case "$1" in
--target)
TARGETS+=("${2:-}")
shift 2
;;
--all-common)
ALL_COMMON=1
shift 1
;;
--cache-dir)
CACHE_DIR="${2:-}"
shift 2
;;
--mcp-version)
MCP_VERSION="${2:-}"
shift 2
;;
--node-version)
NODE_VERSION="${2:-}"
shift 2
;;
--force)
FORCE=1
shift 1
;;
--dry-run)
DRY_RUN=1
shift 1
;;
--no-check)
NO_CHECK=1
shift 1
;;
-h|--help)
sed -n '1,28p' "$0"
exit 0
;;
*)
echo "未知参数: $1" >&2
exit 2
;;
esac
done
if [[ $ALL_COMMON -eq 1 ]]; then
TARGETS=("${COMMON_TARGETS[@]}")
fi
if [[ ${#TARGETS[@]} -eq 0 ]]; then
# Default to current platform
case "$(uname -s)" in
Darwin)
case "$(uname -m)" in
arm64) TARGETS=("aarch64-apple-darwin") ;;
*) TARGETS=("x86_64-apple-darwin") ;;
esac
;;
Linux)
case "$(uname -m)" in
aarch64) TARGETS=("aarch64-unknown-linux-gnu") ;;
armv7l) TARGETS=("aarch64-unknown-linux-gnu") ;;
*) TARGETS=("x86_64-unknown-linux-gnu") ;;
esac
;;
MINGW*|MSYS*|CYGWIN*)
TARGETS=("x86_64-pc-windows-msvc")
;;
esac
fi
# 如果未指定 MCP 版本,从 GitHub 获取最新版本
if [[ -z "$MCP_VERSION" ]]; then
echo "正在从 GitHub 获取 mcp-stdio-proxy 最新版本..."
MCP_VERSION=$(get_latest_mcp_version) || exit 1
echo "检测到最新版本: $MCP_VERSION"
fi
mkdir -p "$CACHE_DIR" "$TMP_DIR"
MANIFEST_FILE="$CACHE_DIR/sidecar-download-manifest.txt"
download_file() {
local url="$1"
local out="$2"
if [[ $DRY_RUN -eq 1 ]]; then
echo "DRY curl -fL \"$url\" -o \"$out\""
return 0
fi
curl -fL "$url" -o "$out"
}
download_first_success() {
local out="$1"
shift
local urls=("$@")
local url
local i=0
LAST_SUCCESS_URL=""
for url in "${urls[@]}"; do
i=$((i + 1))
if [[ $DRY_RUN -eq 1 ]]; then
echo "DRY candidate[$i] -> $url"
download_file "$url" "$out"
LAST_SUCCESS_URL="$url"
return 0
fi
if curl -fL "$url" -o "$out"; then
LAST_SUCCESS_URL="$url"
return 0
fi
echo " WARN 下载失败,尝试下一个候选: $url" >&2
done
return 1
}
extract_archive() {
local archive="$1"
local out_dir="$2"
rm -rf "$out_dir"
mkdir -p "$out_dir"
# 按扩展名选择解压方式Windows 下 MCP 为 .zip必须用 unzip若保存时无扩展名会误走 tar 导致失败)
if [[ "$archive" == *.zip ]]; then
unzip -q -o "$archive" -d "$out_dir"
elif [[ "$archive" == *.tar.xz || "$archive" == *.tar.gz || "$archive" == *.tgz ]]; then
tar -xf "$archive" -C "$out_dir"
else
# 无扩展名或未知格式时用 file 检测
if command -v file &>/dev/null; then
case "$(file -b "$archive")" in
*[Zz]ip*)
unzip -q -o "$archive" -d "$out_dir"
;;
*)
tar -xf "$archive" -C "$out_dir"
;;
esac
else
tar -xf "$archive" -C "$out_dir"
fi
fi
}
if [[ $DRY_RUN -eq 0 ]]; then
{
echo "# sidecar download manifest"
echo "# generated_at=$(date -u +%Y-%m-%dT%H:%M:%SZ)"
echo "# mcp_version=$MCP_VERSION node_version=$NODE_VERSION"
echo "# cache_dir=$CACHE_DIR"
echo "# format: target component filename source"
} > "$MANIFEST_FILE"
fi
for TARGET in "${TARGETS[@]}"; do
MCP_ARTIFACT_CANDIDATES=()
case "$TARGET" in
x86_64-pc-windows-msvc)
MCP_ARTIFACT_CANDIDATES=("mcp-stdio-proxy-x86_64-pc-windows-msvc.zip")
MCP_BIN_NAME="mcp-proxy.exe"
NODE_ARTIFACT="node-v${NODE_VERSION}-win-x64.zip"
NODE_BIN_NAME="node.exe"
DEST_EXT=".exe"
;;
aarch64-pc-windows-msvc)
MCP_ARTIFACT_CANDIDATES=(
"mcp-stdio-proxy-aarch64-pc-windows-msvc.zip"
"mcp-stdio-proxy-x86_64-pc-windows-msvc.zip"
)
MCP_BIN_NAME="mcp-proxy.exe"
NODE_ARTIFACT="node-v${NODE_VERSION}-win-arm64.zip"
NODE_BIN_NAME="node.exe"
DEST_EXT=".exe"
;;
x86_64-apple-darwin)
MCP_ARTIFACT_CANDIDATES=("mcp-stdio-proxy-x86_64-apple-darwin.tar.xz")
MCP_BIN_NAME="mcp-proxy"
NODE_ARTIFACT="node-v${NODE_VERSION}-darwin-x64.tar.gz"
NODE_BIN_NAME="node"
DEST_EXT=""
;;
aarch64-apple-darwin)
MCP_ARTIFACT_CANDIDATES=("mcp-stdio-proxy-aarch64-apple-darwin.tar.xz")
MCP_BIN_NAME="mcp-proxy"
NODE_ARTIFACT="node-v${NODE_VERSION}-darwin-arm64.tar.gz"
NODE_BIN_NAME="node"
DEST_EXT=""
;;
x86_64-unknown-linux-gnu)
MCP_ARTIFACT_CANDIDATES=("mcp-stdio-proxy-x86_64-unknown-linux-gnu.tar.xz")
MCP_BIN_NAME="mcp-proxy"
NODE_ARTIFACT="node-v${NODE_VERSION}-linux-x64.tar.xz"
NODE_BIN_NAME="node"
DEST_EXT=""
;;
aarch64-unknown-linux-gnu)
MCP_ARTIFACT_CANDIDATES=("mcp-stdio-proxy-aarch64-unknown-linux-gnu.tar.xz")
MCP_BIN_NAME="mcp-proxy"
NODE_ARTIFACT="node-v${NODE_VERSION}-linux-arm64.tar.xz"
NODE_BIN_NAME="node"
DEST_EXT=""
;;
*)
echo "不支持的 target: $TARGET" >&2
exit 2
;;
esac
MCP_CACHE="$CACHE_DIR/mcp-proxy-${TARGET}${DEST_EXT}"
NODE_CACHE="$CACHE_DIR/node-runtime-${TARGET}${DEST_EXT}"
# 从第一个候选 artifact 得到压缩包后缀(.zip 或 .tar.xz保证 extract_archive 能正确选择 unzip/tar
MCP_ARCHIVE_SUFFIX="${MCP_ARTIFACT_CANDIDATES[0]#*${TARGET}}"
MCP_PKG="$TMP_DIR/mcp-stdio-proxy-${TARGET}${MCP_ARCHIVE_SUFFIX}"
# 缓存版本记录文件
MCP_VERSION_FILE="$CACHE_DIR/mcp-proxy-${TARGET}.version"
echo "target: $TARGET"
echo " cache dir : $CACHE_DIR"
echo " mcp version : $MCP_VERSION"
echo " node version: $NODE_VERSION"
# 检查是否需要重新下载force标志、缓存文件不存在、或版本不一致
NEED_DOWNLOAD=0
if [[ $FORCE -eq 1 ]]; then
NEED_DOWNLOAD=1
rm -f "$MCP_VERSION_FILE"
echo " FORCE 强制重新下载"
elif [[ ! -f "$MCP_CACHE" || ! -f "$NODE_CACHE" ]]; then
NEED_DOWNLOAD=1
echo " 缓存文件不存在,需要下载"
elif [[ -f "$MCP_VERSION_FILE" ]]; then
CACHED_VERSION=$(cat "$MCP_VERSION_FILE" 2>/dev/null || echo "")
if [[ "$CACHED_VERSION" != "$MCP_VERSION" ]]; then
NEED_DOWNLOAD=1
echo " 版本不一致 (缓存: $CACHED_VERSION, 最新: $MCP_VERSION),需要更新"
fi
else
# 缓存文件存在但没有版本记录,尝试从二进制获取版本
CACHED_VERSION=$("$MCP_CACHE" -V 2>/dev/null | awk '{print $2}' || echo "")
if [[ -z "$CACHED_VERSION" ]]; then
# 无法获取版本,保守起见重新下载
NEED_DOWNLOAD=1
echo " 无法获取缓存版本,需要重新下载"
elif [[ "$CACHED_VERSION" != "$MCP_VERSION" ]]; then
NEED_DOWNLOAD=1
echo " 版本不一致 (缓存: $CACHED_VERSION, 最新: $MCP_VERSION),需要更新"
else
# 版本一致,补写版本记录文件
echo "$MCP_VERSION" > "$MCP_VERSION_FILE"
fi
fi
if [[ $NEED_DOWNLOAD -eq 0 ]]; then
echo " SKIP 已存在缓存: $(basename "$MCP_CACHE"), $(basename "$NODE_CACHE")"
else
NODE_URL="https://nodejs.org/dist/v${NODE_VERSION}/${NODE_ARTIFACT}"
NODE_PKG="$TMP_DIR/${NODE_ARTIFACT}"
MCP_URLS=()
for artifact in "${MCP_ARTIFACT_CANDIDATES[@]}"; do
MCP_URLS+=("https://qiming-packages.oss-rg-china-mainland.aliyuncs.com/mcp-stdio-proxy/v${MCP_VERSION}/${artifact}")
done
if ! download_first_success "$MCP_PKG" "${MCP_URLS[@]}"; then
echo "下载 mcp-proxy 失败 (target=$TARGET, version=$MCP_VERSION)" >&2
exit 1
fi
MCP_SELECTED_URL="$LAST_SUCCESS_URL"
download_file "$NODE_URL" "$NODE_PKG"
if [[ $DRY_RUN -eq 1 ]]; then
echo " DRY copy -> $MCP_CACHE"
echo " DRY copy -> $NODE_CACHE"
echo ""
continue
fi
MCP_EXTRACT_DIR="$TMP_DIR/extract-mcp-${TARGET}"
NODE_EXTRACT_DIR="$TMP_DIR/extract-node-${TARGET}"
extract_archive "$MCP_PKG" "$MCP_EXTRACT_DIR"
extract_archive "$NODE_PKG" "$NODE_EXTRACT_DIR"
MCP_SRC="$(find "$MCP_EXTRACT_DIR" -type f -name "$MCP_BIN_NAME" | head -n 1)"
NODE_SRC="$(find "$NODE_EXTRACT_DIR" -type f -name "$NODE_BIN_NAME" | head -n 1)"
if [[ -z "$MCP_SRC" || -z "$NODE_SRC" ]]; then
echo "解压后未找到目标二进制 (target=$TARGET)" >&2
exit 1
fi
cp -f "$MCP_SRC" "$MCP_CACHE"
cp -f "$NODE_SRC" "$NODE_CACHE"
chmod +x "$MCP_CACHE" "$NODE_CACHE" || true
# 保存版本号到版本文件
echo "$MCP_VERSION" > "$MCP_VERSION_FILE"
echo " DONE cache $(basename "$MCP_CACHE")"
echo " DONE cache $(basename "$NODE_CACHE")"
echo "$TARGET mcp-proxy $(basename "$MCP_CACHE") $MCP_SELECTED_URL" >> "$MANIFEST_FILE"
echo "$TARGET node-runtime $(basename "$NODE_CACHE") $NODE_URL" >> "$MANIFEST_FILE"
fi
echo ""
done
echo "完成。"
echo " cache: $CACHE_DIR"
if [[ $NO_CHECK -eq 0 && $DRY_RUN -eq 0 ]]; then
echo ""
echo "开始自动校验下载项 sidecar..."
for t in "${TARGETS[@]}"; do
"$SCRIPT_DIR/check-sidecars.sh" --downloaded-only --target "$t" --dir "$CACHE_DIR"
done
fi