4561ee4cc3
wintun.net / github 下载偶发超时(curl 28)。retry 3→8、加 --retry-all-errors + --retry-connrefused + --connect-timeout 20,拉长重试窗口以命中可达时刻。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013nMthbVEmQquxBRKb9Fj8u
237 lines
10 KiB
Bash
237 lines
10 KiB
Bash
#!/usr/bin/env bash
|
||
# fetch-desktop-bin.sh — 从 sing-box GitHub Release 下载桌面平台二进制
|
||
#
|
||
# 用法: ./fetch-desktop-bin.sh <os> <arch> [--force]
|
||
# os : linux | darwin | windows
|
||
# arch : amd64 | arm64
|
||
# --force 忽略已有产物,强制重新下载
|
||
#
|
||
# 产物目录: dist/desktop/<os>-<arch>/
|
||
# Linux/macOS : sing-box
|
||
# Windows : sing-box.exe + wintun.dll
|
||
#
|
||
# SHA256 校验: 从官方 Release 下载 checksums.txt 后逐项验证。
|
||
# 校验通过后,checksums.txt 缓存于产物目录,可离线复核。
|
||
#
|
||
# Wintun (Windows 专用):
|
||
# 版本由 VERSION 中 WINTUN_VERSION 控制。
|
||
# 从 https://www.wintun.net/builds/wintun-<ver>.zip 下载,
|
||
# 校验 zip 的 SHA256(见下方 WINTUN_SHA256 变量),
|
||
# 提取对应 arch 的 wintun.dll 到产物目录。
|
||
#
|
||
# 幂等: 产物已存在且 SHA256 校验通过则跳过下载(传 --force 强制重下)
|
||
|
||
set -euo pipefail
|
||
|
||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||
|
||
# ── 加载版本锚点 ─────────────────────────────────────────────────────────────
|
||
# shellcheck source=VERSION
|
||
. "${SCRIPT_DIR}/VERSION"
|
||
|
||
# ── Wintun ZIP SHA256(对应 WINTUN_VERSION=0.14.1)─────────────────────────
|
||
# 更新 WINTUN_VERSION 时须同步更新此值:
|
||
# curl -sL https://www.wintun.net/builds/wintun-<ver>.zip | sha256sum
|
||
WINTUN_SHA256="07c256185d6ee3652e09fa55c0b673e2624b565e02c4b9091c79ca7d2f24ef51"
|
||
|
||
# ── 参数解析 ─────────────────────────────────────────────────────────────────
|
||
if [[ $# -lt 2 ]]; then
|
||
printf 'Usage: %s <os> <arch> [--force]\n' "$0" >&2
|
||
printf ' os : linux | darwin | windows\n' >&2
|
||
printf ' arch: amd64 | arm64\n' >&2
|
||
exit 1
|
||
fi
|
||
|
||
TARGET_OS="$1"
|
||
TARGET_ARCH="$2"
|
||
FORCE=false
|
||
shift 2
|
||
for arg in "$@"; do
|
||
case "${arg}" in
|
||
--force) FORCE=true ;;
|
||
*) printf 'Unknown argument: %s\n' "${arg}" >&2; exit 1 ;;
|
||
esac
|
||
done
|
||
|
||
# ── 参数验证 ─────────────────────────────────────────────────────────────────
|
||
case "${TARGET_OS}" in
|
||
linux|darwin|windows) ;;
|
||
*) printf '✗ 不支持的 OS: %s(支持: linux | darwin | windows)\n' "${TARGET_OS}" >&2; exit 1 ;;
|
||
esac
|
||
case "${TARGET_ARCH}" in
|
||
amd64|arm64) ;;
|
||
*) printf '✗ 不支持的 arch: %s(支持: amd64 | arm64)\n' "${TARGET_ARCH}" >&2; exit 1 ;;
|
||
esac
|
||
|
||
# ── 路径与文件名配置 ─────────────────────────────────────────────────────────
|
||
DIST_DIR="${SCRIPT_DIR}/dist/desktop/${TARGET_OS}-${TARGET_ARCH}"
|
||
CACHE_DIR="${SCRIPT_DIR}/.build/cache"
|
||
|
||
VERSION_BARE="${SINGBOX_VERSION#v}" # 去掉前缀 v(如 v1.13.12 → 1.13.12)
|
||
|
||
if [[ "${TARGET_OS}" == "windows" ]]; then
|
||
ARCHIVE_EXT="zip"
|
||
BIN_NAME="sing-box.exe"
|
||
else
|
||
ARCHIVE_EXT="tar.gz"
|
||
BIN_NAME="sing-box"
|
||
fi
|
||
|
||
ARCHIVE_STEM="sing-box-${VERSION_BARE}-${TARGET_OS}-${TARGET_ARCH}"
|
||
ARCHIVE_FILE="${ARCHIVE_STEM}.${ARCHIVE_EXT}"
|
||
|
||
GITHUB_BASE="https://github.com/SagerNet/sing-box/releases/download/${SINGBOX_VERSION}"
|
||
ARCHIVE_URL="${GITHUB_BASE}/${ARCHIVE_FILE}"
|
||
|
||
# ── sing-box 压缩包 SHA256(内置 pin)──────────────────────────────────────────
|
||
# sing-box 自 v1.13 起的 Release 不再发布 checksums.txt,故改为脚本内置各压缩包
|
||
# 的 SHA256。更新 SINGBOX_VERSION 时必须同步更新下表(case 键含版本号,未更新会
|
||
# 命中 *) 分支并报错,以防用错版本的哈希):
|
||
# curl -fsSL "${GITHUB_BASE}/<archive>" | shasum -a 256
|
||
EXPECTED_HASH=""
|
||
case "${ARCHIVE_FILE}" in
|
||
sing-box-1.13.12-windows-amd64.zip) EXPECTED_HASH="e93fc531134eb1beb4efa3c74990a24e48456098a31c03b60d5ddf17f223cf98" ;;
|
||
sing-box-1.13.12-darwin-arm64.tar.gz) EXPECTED_HASH="43eef86f0ea4a79c3696974f397a963c46a457ee46d1ffac9aa913944a5fc986" ;;
|
||
sing-box-1.13.12-darwin-amd64.tar.gz) EXPECTED_HASH="f3275316451bf1983bc059599c69c8ed0232d53a619d15cfd535f95cc9a4477a" ;;
|
||
sing-box-1.13.12-linux-amd64.tar.gz) EXPECTED_HASH="1540533adb3df24f5ad5f14b5c7ca3dbc2401b10a1c1eb278fcadcada47ec6c4" ;;
|
||
esac
|
||
|
||
OUT_BIN="${DIST_DIR}/${BIN_NAME}"
|
||
|
||
# ── 工具检查 ─────────────────────────────────────────────────────────────────
|
||
_need() {
|
||
if ! command -v "$1" >/dev/null 2>&1; then
|
||
printf '✗ 依赖未找到: %s\n' "$1" >&2; exit 1
|
||
fi
|
||
}
|
||
_need curl
|
||
_need sha256sum || _need shasum # macOS 用 shasum,Linux 用 sha256sum
|
||
|
||
# 兼容 macOS (shasum -a 256) 和 Linux (sha256sum)
|
||
_sha256() {
|
||
if command -v sha256sum >/dev/null 2>&1; then
|
||
sha256sum "$1" | awk '{print $1}'
|
||
else
|
||
shasum -a 256 "$1" | awk '{print $1}'
|
||
fi
|
||
}
|
||
|
||
# ── 幂等检查(已有产物则跳过;--force 强制重下)────────────────────────────────
|
||
if [[ "${FORCE}" == false && -f "${OUT_BIN}" ]]; then
|
||
printf '✓ %s 已存在,跳过下载\n' "${OUT_BIN}"
|
||
printf ' 当前 SHA256: %s\n' "$(_sha256 "${OUT_BIN}")"
|
||
printf ' (传 --force 强制重新下载并校验压缩包)\n'
|
||
exit 0
|
||
fi
|
||
|
||
# ── 创建目录 ─────────────────────────────────────────────────────────────────
|
||
mkdir -p "${DIST_DIR}"
|
||
mkdir -p "${CACHE_DIR}"
|
||
|
||
ARCHIVE_CACHE="${CACHE_DIR}/${ARCHIVE_FILE}"
|
||
|
||
# 未在内置 pin 表中找到该压缩包的 SHA256(多半是 SINGBOX_VERSION 升级后没同步哈希)
|
||
if [[ -z "${EXPECTED_HASH}" ]]; then
|
||
printf '✗ 脚本内未内置 %s 的 SHA256\n' "${ARCHIVE_FILE}" >&2
|
||
printf ' 升级 SINGBOX_VERSION 后需在本脚本 case 表补该压缩包哈希:\n' >&2
|
||
printf ' curl -fsSL "%s" | shasum -a 256\n' "${ARCHIVE_URL}" >&2
|
||
exit 1
|
||
fi
|
||
|
||
# ── 下载二进制压缩包 ──────────────────────────────────────────────────────────
|
||
printf '==> 下载 %s…\n' "${ARCHIVE_FILE}"
|
||
curl -fSL --retry 8 --retry-delay 5 --retry-connrefused --retry-all-errors --connect-timeout 20 \
|
||
-o "${ARCHIVE_CACHE}" \
|
||
"${ARCHIVE_URL}"
|
||
|
||
# ── SHA256 校验(对压缩包,比对内置 pin)──────────────────────────────────────
|
||
printf '==> 校验 SHA256…\n'
|
||
|
||
ACTUAL_HASH="$(_sha256 "${ARCHIVE_CACHE}")"
|
||
if [[ "${ACTUAL_HASH}" != "${EXPECTED_HASH}" ]]; then
|
||
printf '✗ SHA256 不匹配!\n' >&2
|
||
printf ' 期望: %s\n' "${EXPECTED_HASH}" >&2
|
||
printf ' 实际: %s\n' "${ACTUAL_HASH}" >&2
|
||
rm -f "${ARCHIVE_CACHE}"
|
||
exit 1
|
||
fi
|
||
printf ' ✓ SHA256 匹配: %s\n' "${ACTUAL_HASH}"
|
||
|
||
# ── 解压二进制 ────────────────────────────────────────────────────────────────
|
||
printf '==> 解压 → %s\n' "${DIST_DIR}"
|
||
EXTRACT_TMP="${CACHE_DIR}/extract-${ARCHIVE_STEM}"
|
||
rm -rf "${EXTRACT_TMP}"
|
||
mkdir -p "${EXTRACT_TMP}"
|
||
|
||
if [[ "${ARCHIVE_EXT}" == "zip" ]]; then
|
||
if ! command -v unzip >/dev/null 2>&1; then
|
||
printf '✗ unzip 未找到\n' >&2; exit 1
|
||
fi
|
||
unzip -q "${ARCHIVE_CACHE}" -d "${EXTRACT_TMP}"
|
||
else
|
||
tar -xzf "${ARCHIVE_CACHE}" -C "${EXTRACT_TMP}"
|
||
fi
|
||
|
||
# sing-box 压缩包内层目录名与 ARCHIVE_STEM 相同
|
||
INNER_DIR="${EXTRACT_TMP}/${ARCHIVE_STEM}"
|
||
if [[ ! -f "${INNER_DIR}/${BIN_NAME}" ]]; then
|
||
printf '✗ 压缩包中未找到 %s/%s\n' "${ARCHIVE_STEM}" "${BIN_NAME}" >&2
|
||
exit 1
|
||
fi
|
||
|
||
cp "${INNER_DIR}/${BIN_NAME}" "${OUT_BIN}"
|
||
chmod 755 "${OUT_BIN}"
|
||
|
||
printf '\n✓ sing-box 二进制: %s\n' "${OUT_BIN}"
|
||
printf ' 版本验证: %s version\n' "${OUT_BIN}"
|
||
|
||
# ── Windows 额外处理: wintun.dll ─────────────────────────────────────────────
|
||
if [[ "${TARGET_OS}" == "windows" ]]; then
|
||
printf '\n==> Windows: 下载 wintun.dll (v%s)…\n' "${WINTUN_VERSION}"
|
||
|
||
WINTUN_ZIP_NAME="wintun-${WINTUN_VERSION}.zip"
|
||
WINTUN_URL="https://www.wintun.net/builds/${WINTUN_ZIP_NAME}"
|
||
WINTUN_ZIP_CACHE="${CACHE_DIR}/${WINTUN_ZIP_NAME}"
|
||
WINTUN_OUT="${DIST_DIR}/wintun.dll"
|
||
|
||
if [[ "${FORCE}" == false && -f "${WINTUN_OUT}" ]]; then
|
||
printf '✓ wintun.dll 已存在,跳过(传 --force 重新下载)\n'
|
||
else
|
||
curl -fSL --retry 8 --retry-delay 5 --retry-connrefused --retry-all-errors --connect-timeout 20 \
|
||
-o "${WINTUN_ZIP_CACHE}" \
|
||
"${WINTUN_URL}"
|
||
|
||
printf '==> 校验 wintun.zip SHA256…\n'
|
||
WINTUN_ACTUAL="$(_sha256 "${WINTUN_ZIP_CACHE}")"
|
||
if [[ "${WINTUN_ACTUAL}" != "${WINTUN_SHA256}" ]]; then
|
||
printf '✗ wintun.zip SHA256 不匹配!\n' >&2
|
||
printf ' 期望: %s\n' "${WINTUN_SHA256}" >&2
|
||
printf ' 实际: %s\n' "${WINTUN_ACTUAL}" >&2
|
||
printf ' 如果 WINTUN_VERSION 已更新,请同步更新脚本中的 WINTUN_SHA256\n' >&2
|
||
rm -f "${WINTUN_ZIP_CACHE}"
|
||
exit 1
|
||
fi
|
||
printf ' ✓ wintun.zip SHA256 匹配\n'
|
||
|
||
# 提取对应 arch 的 wintun.dll
|
||
# wintun zip 内路径: wintun/bin/<arch>/wintun.dll
|
||
WINTUN_EXTRACT="${CACHE_DIR}/extract-wintun"
|
||
rm -rf "${WINTUN_EXTRACT}"
|
||
mkdir -p "${WINTUN_EXTRACT}"
|
||
unzip -q "${WINTUN_ZIP_CACHE}" -d "${WINTUN_EXTRACT}"
|
||
|
||
WINTUN_DLL_SRC="${WINTUN_EXTRACT}/wintun/bin/${TARGET_ARCH}/wintun.dll"
|
||
if [[ ! -f "${WINTUN_DLL_SRC}" ]]; then
|
||
printf '✗ wintun zip 中未找到 %s arch 的 wintun.dll\n' "${TARGET_ARCH}" >&2
|
||
exit 1
|
||
fi
|
||
|
||
cp "${WINTUN_DLL_SRC}" "${WINTUN_OUT}"
|
||
printf '✓ wintun.dll: %s\n' "${WINTUN_OUT}"
|
||
fi
|
||
fi
|
||
|
||
# ── 最终输出摘要 ─────────────────────────────────────────────────────────────
|
||
printf '\n=== 产物清单: %s ===\n' "${DIST_DIR}"
|
||
ls -lh "${DIST_DIR}/"
|