Files
cellular-proxy/scripts/publish-binaries-to-gitea.sh
T

195 lines
6.1 KiB
Bash
Executable File
Raw 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.
#!/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"