195 lines
6.1 KiB
Bash
Executable File
195 lines
6.1 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
# 将 sing-box 官方 release 预下载并上传到本仓库 Gitea Release
|
||
# 用法(维护机):
|
||
# set -a; source ~/.hermes/env/gitea.env; set +a
|
||
# ./scripts/publish-binaries-to-gitea.sh
|
||
# SING_BOX_VERSION=1.11.7 ARCHS='amd64 arm64 armv7' ./scripts/publish-binaries-to-gitea.sh
|
||
#
|
||
# GitHub 下载默认经 https://git.86482425.xyz
|
||
# 上传走 multipart;>约 8MB 可能被反向代理 504,自动改 1MB 分片上传
|
||
set -euo pipefail
|
||
|
||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||
: "${GITEA_URL:?请先 source ~/.hermes/env/gitea.env}"
|
||
: "${GITEA_TOKEN:?请先 source ~/.hermes/env/gitea.env}"
|
||
|
||
OWNER="${GITEA_OWNER:-Hermes}"
|
||
REPO="${GITEA_REPO:-cellular-proxy}"
|
||
VER="${SING_BOX_VERSION:-1.11.7}"
|
||
TAG="${SING_BOX_RELEASE_TAG:-bin-v${VER}}"
|
||
ARCHS="${ARCHS:-amd64 arm64 armv7}"
|
||
GITHUB_PROXY="${GITHUB_PROXY:-https://git.86482425.xyz}"
|
||
PART_BYTES="${PART_BYTES:-1048576}"
|
||
# 经验阈值:整包上传超时则分片(nginx 常见 ~80s)
|
||
WHOLE_MAX_BYTES="${WHOLE_MAX_BYTES:-9000000}"
|
||
TMP="$(mktemp -d)"
|
||
trap 'rm -rf "$TMP"' EXIT
|
||
|
||
log() { printf '[%s] %s\n' "$(date '+%F %T')" "$*" >&2; }
|
||
|
||
download() {
|
||
local url="$1" dest="$2"
|
||
log "下载 $url"
|
||
curl -fL --retry 3 --connect-timeout 20 --max-time 300 -o "$dest" "$url"
|
||
}
|
||
|
||
github_fetch() {
|
||
local raw="$1" dest="$2"
|
||
local proxied="${GITHUB_PROXY%/}/${raw}"
|
||
if [[ -n "${GITHUB_PROXY:-}" ]]; then
|
||
if download "$proxied" "$dest"; then
|
||
return 0
|
||
fi
|
||
log "代理失败,直连"
|
||
fi
|
||
download "$raw" "$dest"
|
||
}
|
||
|
||
API="$GITEA_URL/api/v1/repos/$OWNER/$REPO"
|
||
auth=(-H "Authorization: token $GITEA_TOKEN")
|
||
|
||
delete_asset() {
|
||
local id="$1"
|
||
# Gitea 正确路径:/releases/{id}/assets/{asset_id}
|
||
curl -sS -X DELETE "${auth[@]}" -o /dev/null -w "DEL %{http_code}\n" \
|
||
"$API/releases/$REL_ID/assets/$id" || true
|
||
}
|
||
|
||
upload_file() {
|
||
local fpath="$1" fname="$2"
|
||
local code
|
||
code=$(curl -sS --http1.1 --max-time 300 \
|
||
"${auth[@]}" \
|
||
-F "attachment=@${fpath};filename=${fname}" \
|
||
-o "$TMP/up_${fname}.json" \
|
||
-w '%{http_code}' \
|
||
"$API/releases/$REL_ID/assets?name=${fname}" || echo 000)
|
||
if [[ "$code" != "201" && "$code" != "200" ]]; then
|
||
log "上传失败 $fname HTTP $code"
|
||
head -c 300 "$TMP/up_${fname}.json" 2>/dev/null || true
|
||
return 1
|
||
fi
|
||
python3 - <<PY
|
||
import json
|
||
d=json.load(open("$TMP/up_${fname}.json"))
|
||
assert d.get("name")=="$fname", d
|
||
print("asset ok:", d.get("name"), "size=", d.get("size"))
|
||
PY
|
||
}
|
||
|
||
ensure_release() {
|
||
local code
|
||
code=$(curl -sS "${auth[@]}" -o "$TMP/rel.json" -w '%{http_code}' "$API/releases/tags/$TAG")
|
||
if [[ "$code" == "200" ]]; then
|
||
REL_ID=$(python3 -c 'import json; print(json.load(open("'"$TMP"'/rel.json"))["id"])')
|
||
log "已有 Release tag=$TAG id=$REL_ID"
|
||
else
|
||
log "创建 Release tag=$TAG"
|
||
curl -sS "${auth[@]}" -H 'Content-Type: application/json' \
|
||
-d "$(python3 - <<PY
|
||
import json
|
||
print(json.dumps({
|
||
"tag_name": "$TAG",
|
||
"name": "sing-box $VER prebundled binaries",
|
||
"body": "预置 sing-box v$VER。安装端优先从此 Release 下载,无需访问 GitHub。\\n架构: $ARCHS\\n大文件可能以 .part-XX 分片提供。",
|
||
"draft": False,
|
||
"prerelease": False,
|
||
"target_commitish": "main",
|
||
}))
|
||
PY
|
||
)" \
|
||
-o "$TMP/rel_create.json" \
|
||
"$API/releases"
|
||
REL_ID=$(python3 -c 'import json,sys; d=json.load(open("'"$TMP"'/rel_create.json"));
|
||
print(d.get("id") or "");
|
||
assert d.get("id"), d')
|
||
log "Release id=$REL_ID"
|
||
fi
|
||
}
|
||
|
||
delete_named_assets() {
|
||
local prefix="$1"
|
||
curl -sS "${auth[@]}" "$API/releases/$REL_ID" -o "$TMP/rel_full.json"
|
||
python3 - <<PY
|
||
import json,subprocess,os
|
||
d=json.load(open("$TMP/rel_full.json"))
|
||
prefix="$prefix"
|
||
for a in d.get("assets") or []:
|
||
n=a.get("name") or ""
|
||
if n==prefix or n.startswith(prefix+".") or n.startswith(prefix+".part-") or n==prefix+".SHA256SUMS" or (prefix.endswith(".tar.gz") and n=="SHA256SUMS" and "armv7" in prefix):
|
||
print("delete", a["id"], n)
|
||
subprocess.check_call(["bash","-lc", f'curl -sS -X DELETE -H "Authorization: token $GITEA_TOKEN" -o /dev/null -w "DEL %{{http_code}}\\n" "$API/releases/$REL_ID/assets/{a["id"]}"'])
|
||
PY
|
||
}
|
||
|
||
upload_whole_or_parts() {
|
||
local fpath="$1" fname="$2"
|
||
local size
|
||
size=$(stat -c%s "$fpath")
|
||
delete_named_assets "$fname"
|
||
|
||
if [[ "$size" -le "$WHOLE_MAX_BYTES" ]]; then
|
||
log "整包上传 $fname ($size bytes)"
|
||
if upload_file "$fpath" "$fname"; then
|
||
return 0
|
||
fi
|
||
log "整包失败,改分片"
|
||
else
|
||
log "文件 $size > $WHOLE_MAX_BYTES,直接分片上传 $fname"
|
||
fi
|
||
|
||
local pdir="$TMP/parts_${fname}"
|
||
rm -rf "$pdir"
|
||
mkdir -p "$pdir"
|
||
split -b "$PART_BYTES" -d -a 2 "$fpath" "$pdir/${fname}.part-"
|
||
(
|
||
cd "$pdir"
|
||
# 清单使用纯文件名,便于客户端校验
|
||
sha256sum "${fname}.part-"* > "${fname}.SHA256SUMS"
|
||
)
|
||
local p
|
||
for p in "$pdir/${fname}.part-"* "$pdir/${fname}.SHA256SUMS"; do
|
||
bn=$(basename "$p")
|
||
log "分片上传 $bn ($(stat -c%s "$p") bytes)"
|
||
upload_file "$p" "$bn"
|
||
done
|
||
}
|
||
|
||
# 1) 下载
|
||
for arch in $ARCHS; do
|
||
name="sing-box-${VER}-linux-${arch}"
|
||
raw="https://github.com/SagerNet/sing-box/releases/download/v${VER}/${name}.tar.gz"
|
||
dest="$TMP/${name}.tar.gz"
|
||
github_fetch "$raw" "$dest"
|
||
tar -tzf "$dest" | head -3 >/dev/null
|
||
ls -lh "$dest"
|
||
done
|
||
|
||
# 2) release
|
||
ensure_release
|
||
|
||
# 3) 上传
|
||
for arch in $ARCHS; do
|
||
fname="sing-box-${VER}-linux-${arch}.tar.gz"
|
||
upload_whole_or_parts "$TMP/$fname" "$fname"
|
||
done
|
||
|
||
# 4) 公开下载自检
|
||
for arch in $ARCHS; do
|
||
fname="sing-box-${VER}-linux-${arch}.tar.gz"
|
||
url="$GITEA_URL/$OWNER/$REPO/releases/download/$TAG/$fname"
|
||
code=$(curl -sS -o /dev/null -w '%{http_code}' -L --max-time 30 "$url" || true)
|
||
if [[ "$code" == "200" || "$code" == "206" ]]; then
|
||
log "公开整包 OK $url -> $code"
|
||
else
|
||
# 分片?
|
||
p0="$GITEA_URL/$OWNER/$REPO/releases/download/$TAG/${fname}.part-00"
|
||
code2=$(curl -sS -o /dev/null -w '%{http_code}' -L --max-time 30 "$p0" || true)
|
||
log "整包 $code,分片 part-00 -> $code2 ($p0)"
|
||
fi
|
||
done
|
||
|
||
log "完成。安装端优先:"
|
||
log " $GITEA_URL/$OWNER/$REPO/releases/download/$TAG/sing-box-${VER}-linux-<arch>.tar.gz"
|
||
log " 或分片: .../${fname}.part-XX + .../${fname}.SHA256SUMS"
|