#!/usr/bin/env bash # 仅下载/安装 sing-box(轻量核心) # 优先级:local → 系统已有 → Gitea Release(整包/分片)→ GitHub 代理 → 直连 GitHub set -euo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" # shellcheck source=lib.sh source "$ROOT_DIR/scripts/lib.sh" load_config need_root ensure_dirs mkdir -p /var/lib/cellular-proxy ARCH="$(arch_go)" TMP="$(mktemp -d)" trap 'rm -rf "$TMP"' EXIT GITHUB_PROXY="${GITHUB_PROXY:-https://git.86482425.xyz}" GITEA_BASE="${GITEA_BASE:-https://gitea.chickliu.fun}" GITEA_OWNER="${GITEA_OWNER:-Hermes}" GITEA_REPO="${GITEA_REPO:-cellular-proxy}" SING_BOX_RELEASE_TAG="${SING_BOX_RELEASE_TAG:-bin-v${SING_BOX_VERSION}}" # gitea | github | auto | local | release SING_BOX_SOURCE="${SING_BOX_SOURCE:-auto}" github_url() { local raw="$1" local proxy="${GITHUB_PROXY:-}" if [[ -z "$proxy" ]]; then echo "$raw" return fi proxy="${proxy%/}" case "$raw" in "${proxy}"/*) echo "$raw"; return ;; esac echo "${proxy}/${raw}" } download() { local url="$1" dest="$2" info "下载: $url" if command -v curl >/dev/null 2>&1; then curl -fL --retry 3 --connect-timeout 20 --max-time 300 -o "$dest" "$url" elif command -v wget >/dev/null 2>&1; then wget -O "$dest" "$url" else die "需要 curl 或 wget" fi } # 静默探测 URL 是否可下(不写盘) url_ok() { local url="$1" if command -v curl >/dev/null 2>&1; then curl -fsSIL --connect-timeout 8 --max-time 20 -o /dev/null "$url" 2>/dev/null else return 1 fi } download_github() { local raw="$1" dest="$2" local proxied proxied="$(github_url "$raw")" if [[ "$proxied" != "$raw" ]]; then info "经 GitHub 代理下载: $proxied" if download "$proxied" "$dest"; then return 0 fi warn "代理下载失败,尝试直连 GitHub: $raw" fi download "$raw" "$dest" } install_from_tarball() { local tgz="$1" ver="$2" arch="$3" local name="sing-box-${ver}-linux-${arch}" tar -xzf "$tgz" -C "$TMP" if [[ -x "$TMP/${name}/sing-box" ]]; then install -m 0755 "$TMP/${name}/sing-box" "$INSTALL_DIR/bin/sing-box" elif [[ -x "$TMP/sing-box" ]]; then install -m 0755 "$TMP/sing-box" "$INSTALL_DIR/bin/sing-box" else local found found="$(find "$TMP" -type f -name sing-box 2>/dev/null | head -1)" [[ -n "$found" && -x "$found" ]] || die "压缩包内未找到 sing-box 可执行文件" install -m 0755 "$found" "$INSTALL_DIR/bin/sing-box" fi } gitea_asset_url() { local fname="$1" echo "${GITEA_BASE%/}/${GITEA_OWNER}/${GITEA_REPO}/releases/download/${SING_BOX_RELEASE_TAG}/${fname}" } # 从 Gitea 拉整包 tar.gz try_download_gitea_whole() { local ver="$1" arch="$2" dest="$3" local name url name="sing-box-${ver}-linux-${arch}.tar.gz" url="$(gitea_asset_url "$name")" info "尝试 Gitea 预置整包: $url" if download "$url" "$dest"; then return 0 fi return 1 } # 代理 504 时可能只提供分片:name.tar.gz.part-00.. + name.tar.gz.SHA256SUMS try_download_gitea_parts() { local ver="$1" arch="$2" dest="$3" local base name sums_url part_url part_dir i max name="sing-box-${ver}-linux-${arch}.tar.gz" base="${GITEA_BASE%/}/${GITEA_OWNER}/${GITEA_REPO}/releases/download/${SING_BOX_RELEASE_TAG}" sums_url="${base}/${name}.SHA256SUMS" # 兼容旧名 SHA256SUMS(仅 armv7 曾用) part_dir="$TMP/parts_${arch}" mkdir -p "$part_dir" if download "$sums_url" "$part_dir/SHA256SUMS" 2>/dev/null; then info "发现分片清单: $sums_url" elif [[ "$arch" == "armv7" ]] && download "$(gitea_asset_url SHA256SUMS)" "$part_dir/SHA256SUMS" 2>/dev/null; then info "使用兼容清单 SHA256SUMS" else return 1 fi # 从清单提取 part 文件名(去掉路径前缀) mapfile -t parts < <(awk '{print $2}' "$part_dir/SHA256SUMS" | sed 's|^\./||' | grep -E '\.part-[0-9]+$' || true) if [[ ${#parts[@]} -eq 0 ]]; then # 无清单文件名时按 part-00..99 探测 parts=() for i in $(seq -w 0 99); do part_url="${base}/${name}.part-${i}" if url_ok "$part_url"; then parts+=("${name}.part-${i}") else [[ ${#parts[@]} -gt 0 ]] && break fi done fi [[ ${#parts[@]} -gt 0 ]] || return 1 info "Gitea 分片下载 ${#parts[@]} 片 → 合并 $name" : >"$dest" for p in "${parts[@]}"; do p="${p#./}" # 若清单里只有 basename part-xx,补全前缀 case "$p" in *.part-*) ;; *) p="${name}.part-${p##*-}" ;; esac # 清单可能是 relative part name only like sing-box-...part-00 if [[ "$p" != *"/"* && "$p" != ${name}.part-* && "$p" == *.part-* ]]; then : fi part_url="${base}/${p}" if ! download "$part_url" "$part_dir/$p"; then # 尝试无前缀名 local bn bn="$(basename "$p")" part_url="${base}/${bn}" download "$part_url" "$part_dir/$bn" || return 1 p="$bn" fi cat "$part_dir/$p" >>"$dest" done # 可选校验 if command -v sha256sum >/dev/null 2>&1; then ( cd "$part_dir" # 规范化清单路径 sed 's| \./| |; s|^\([a-f0-9]*\) \./|\1 |' SHA256SUMS > SHA256SUMS.norm || cp SHA256SUMS SHA256SUMS.norm if sha256sum -c SHA256SUMS.norm --status 2>/dev/null; then info "分片 SHA256 校验通过" else warn "分片 SHA256 校验跳过/未严格通过(已合并,继续)" fi ) || true fi # 快速完整性:tar 列表 tar -tzf "$dest" >/dev/null return 0 } try_download_gitea() { local ver="$1" arch="$2" dest="$3" if try_download_gitea_whole "$ver" "$arch" "$dest"; then return 0 fi warn "Gitea 整包不可用,尝试分片合并(大文件/代理 504 兜底)" if try_download_gitea_parts "$ver" "$arch" "$dest"; then return 0 fi return 1 } try_download_github_release() { local ver="$1" arch="$2" dest="$3" local name raw_url name="sing-box-${ver}-linux-${arch}" raw_url="https://github.com/SagerNet/sing-box/releases/download/v${ver}/${name}.tar.gz" download_github "$raw_url" "$dest" } if [[ "$SING_BOX_SOURCE" == "local" ]]; then [[ -n "$SING_BOX_BIN" && -x "$SING_BOX_BIN" ]] || die "SING_BOX_SOURCE=local 但 SING_BOX_BIN 无效" install -m 0755 "$SING_BOX_BIN" "$INSTALL_DIR/bin/sing-box" info "已安装本地 sing-box" elif command -v sing-box >/dev/null 2>&1 && [[ -z "${FORCE_DOWNLOAD:-}" && "$SING_BOX_SOURCE" != "gitea" && "$SING_BOX_SOURCE" != "github" ]]; then install -m 0755 "$(command -v sing-box)" "$INSTALL_DIR/bin/sing-box" info "复用系统 sing-box: $(command -v sing-box)" else ver="$SING_BOX_VERSION" tgz="$TMP/sb.tgz" ok=0 case "$SING_BOX_SOURCE" in gitea) try_download_gitea "$ver" "$ARCH" "$tgz" || die "Gitea 预置包下载失败(tag=$SING_BOX_RELEASE_TAG arch=$ARCH)" ok=1 ;; github|release) try_download_github_release "$ver" "$ARCH" "$tgz" ok=1 ;; auto|*) if try_download_gitea "$ver" "$ARCH" "$tgz"; then ok=1 info "已从 Gitea Release 获取 sing-box" else warn "Gitea 预置包不可用,回退 GitHub(经 ${GITHUB_PROXY:-直连})" try_download_github_release "$ver" "$ARCH" "$tgz" ok=1 fi ;; esac [[ "$ok" -eq 1 ]] || die "无法下载 sing-box" install_from_tarball "$tgz" "$ver" "$ARCH" info "已安装 sing-box v${ver} (${ARCH})" "$INSTALL_DIR/bin/sing-box" version 2>/dev/null | head -3 || true fi # 安装中文静态 UI mkdir -p "$INSTALL_DIR/ui" if [[ -f "$ROOT_DIR/ui/index.html" ]]; then cp -a "$ROOT_DIR/ui/." "$INSTALL_DIR/ui/" info "已安装内置中文面板 -> $INSTALL_DIR/ui" else warn "未找到 ui/index.html" fi info "二进制就绪:" ls -la "$INSTALL_DIR/bin" du -sh "$INSTALL_DIR/bin" "$INSTALL_DIR/ui" 2>/dev/null || true