mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2026-10-27 16:00:49 +08:00
feat: 支持从 GitHub Release 生成在线升级站点
This commit is contained in:
@@ -5,6 +5,7 @@
|
|||||||
# <site_name>/v1/channels.json
|
# <site_name>/v1/channels.json
|
||||||
# <site_name>/v1/releases.json
|
# <site_name>/v1/releases.json
|
||||||
# <site_name>/v1/bin/<version>/one-kvm-<triple>
|
# <site_name>/v1/bin/<version>/one-kvm-<triple>
|
||||||
|
# 默认从 GitHub Release 下载 deb,提取二进制;也支持本地二进制输入。
|
||||||
#
|
#
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
@@ -17,6 +18,11 @@ RELEASE_CHANNEL="stable"
|
|||||||
STABLE_VERSION=""
|
STABLE_VERSION=""
|
||||||
BETA_VERSION=""
|
BETA_VERSION=""
|
||||||
PUBLISHED_AT="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
|
PUBLISHED_AT="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
|
||||||
|
PUBLISHED_AT_SET=0
|
||||||
|
GITHUB_REPO="mofeng-git/One-KVM"
|
||||||
|
GITHUB_RELEASE=""
|
||||||
|
GITHUB_PROXY="${GITHUB_PROXY:-}"
|
||||||
|
GITHUB_API_PROXY="${GITHUB_API_PROXY:-}"
|
||||||
ARTIFACTS_DIR=""
|
ARTIFACTS_DIR=""
|
||||||
X86_64_BIN=""
|
X86_64_BIN=""
|
||||||
AARCH64_BIN=""
|
AARCH64_BIN=""
|
||||||
@@ -32,10 +38,17 @@ declare -a NOTES=()
|
|||||||
usage() {
|
usage() {
|
||||||
cat <<'EOF'
|
cat <<'EOF'
|
||||||
Usage:
|
Usage:
|
||||||
./scripts/build-update-site.sh --version <x.x.x> [options]
|
./scripts/build-update-site.sh [options]
|
||||||
|
|
||||||
Required:
|
GitHub input (无本地输入时默认使用 latest Release):
|
||||||
--version <x.x.x> Release 版本号(如 0.1.10)
|
--release <tag|url|latest> GitHub Release 标签或完整页面 URL
|
||||||
|
--github-repo <owner/repo> 默认 mofeng-git/One-KVM
|
||||||
|
--github-proxy <url> deb 下载代理前缀,如 https://ghproxy.com
|
||||||
|
代理失败后自动尝试 GitHub 直连
|
||||||
|
--github-api-proxy <url> 可选 GitHub API 代理前缀
|
||||||
|
也可设置 GITHUB_PROXY / GITHUB_API_PROXY
|
||||||
|
--version <x.x.x> GitHub 输入默认从 deb 文件名读取;本地输入必填
|
||||||
|
GitHub 输入指定时校验版本一致
|
||||||
|
|
||||||
Artifact input (二选一,可混用):
|
Artifact input (二选一,可混用):
|
||||||
--artifacts-dir <dir> 自动扫描目录中的标准文件名:
|
--artifacts-dir <dir> 自动扫描目录中的标准文件名:
|
||||||
@@ -50,8 +63,8 @@ Manifest options:
|
|||||||
--release-channel <stable|beta> releases.json 里该版本所属渠道,默认 stable
|
--release-channel <stable|beta> releases.json 里该版本所属渠道,默认 stable
|
||||||
--stable <x.x.x> channels.json 的 stable 指针,默认等于 --version
|
--stable <x.x.x> channels.json 的 stable 指针,默认等于 --version
|
||||||
--beta <x.x.x> channels.json 的 beta 指针,默认等于 --version
|
--beta <x.x.x> channels.json 的 beta 指针,默认等于 --version
|
||||||
--published-at <RFC3339> 发布时间,默认当前 UTC 时间
|
--published-at <RFC3339> 发布时间,默认 GitHub 发布时间(本地输入为当前时间)
|
||||||
--note <text> 发布说明,可重复传入多次
|
--note <text> 发布说明,可重复;指定后覆盖 GitHub 说明
|
||||||
|
|
||||||
Output options:
|
Output options:
|
||||||
--site-name <name> 打包根目录名,默认 one-kvm-update
|
--site-name <name> 打包根目录名,默认 one-kvm-update
|
||||||
@@ -62,6 +75,12 @@ Other:
|
|||||||
-h, --help 显示帮助
|
-h, --help 显示帮助
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
./scripts/build-update-site.sh --release v261001
|
||||||
|
|
||||||
|
./scripts/build-update-site.sh \
|
||||||
|
--release https://github.com/mofeng-git/One-KVM/releases/tag/v261001 \
|
||||||
|
--github-proxy https://ghproxy.com
|
||||||
|
|
||||||
./scripts/build-update-site.sh \
|
./scripts/build-update-site.sh \
|
||||||
--version 0.1.10 \
|
--version 0.1.10 \
|
||||||
--artifacts-dir ./target/release \
|
--artifacts-dir ./target/release \
|
||||||
@@ -104,7 +123,26 @@ is_valid_channel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
while [[ $# -gt 0 ]]; do
|
while [[ $# -gt 0 ]]; do
|
||||||
|
if [[ "$1" != "-h" && "$1" != "--help" ]]; then
|
||||||
|
[[ $# -ge 2 && -n "$2" && "$2" != --* ]] || fail "Missing value for $1"
|
||||||
|
fi
|
||||||
case "$1" in
|
case "$1" in
|
||||||
|
--release | --github-tag)
|
||||||
|
GITHUB_RELEASE="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--github-repo)
|
||||||
|
GITHUB_REPO="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--github-proxy)
|
||||||
|
GITHUB_PROXY="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--github-api-proxy)
|
||||||
|
GITHUB_API_PROXY="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
--version)
|
--version)
|
||||||
VERSION="${2:-}"
|
VERSION="${2:-}"
|
||||||
shift 2
|
shift 2
|
||||||
@@ -123,6 +161,7 @@ while [[ $# -gt 0 ]]; do
|
|||||||
;;
|
;;
|
||||||
--published-at)
|
--published-at)
|
||||||
PUBLISHED_AT="${2:-}"
|
PUBLISHED_AT="${2:-}"
|
||||||
|
PUBLISHED_AT_SET=1
|
||||||
shift 2
|
shift 2
|
||||||
;;
|
;;
|
||||||
--note)
|
--note)
|
||||||
@@ -175,6 +214,128 @@ require_cmd stat
|
|||||||
require_cmd tar
|
require_cmd tar
|
||||||
require_cmd mktemp
|
require_cmd mktemp
|
||||||
|
|
||||||
|
[[ "$SITE_NAME" =~ ^[a-zA-Z0-9][a-zA-Z0-9._-]*$ ]] || fail "Invalid --site-name: ${SITE_NAME}"
|
||||||
|
BUILD_DIR="$(mktemp -d)"
|
||||||
|
trap 'rm -rf "$BUILD_DIR"' EXIT
|
||||||
|
|
||||||
|
download_github() {
|
||||||
|
local url="$1" dest="$2" proxy="$3"
|
||||||
|
local -a candidates=()
|
||||||
|
[[ -z "$proxy" ]] || candidates+=("${proxy%/}/${url}")
|
||||||
|
candidates+=("$url")
|
||||||
|
local candidate
|
||||||
|
for candidate in "${candidates[@]}"; do
|
||||||
|
echo "Downloading: $candidate" >&2
|
||||||
|
if curl --fail --location --show-error --connect-timeout 15 --max-time 300 \
|
||||||
|
--retry 2 --retry-delay 2 --output "${dest}.part" "$candidate"; then
|
||||||
|
# 一些代理返回 HTTP 200 的 HTML 页面,不能当作 deb 或 API 响应。
|
||||||
|
if [[ "$dest" == *.deb ]]; then
|
||||||
|
dpkg-deb --info "${dest}.part" >/dev/null 2>&1 || continue
|
||||||
|
else
|
||||||
|
python3 -c 'import json,sys; r=json.load(open(sys.argv[1])); assert isinstance(r,dict) and isinstance(r.get("assets"),list)' \
|
||||||
|
"${dest}.part" >/dev/null 2>&1 || continue
|
||||||
|
fi
|
||||||
|
mv "${dest}.part" "$dest"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
echo "Download failed or invalid response; trying next source." >&2
|
||||||
|
done
|
||||||
|
fail "Unable to download $url"
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ -n "$GITHUB_RELEASE" || ( -z "$ARTIFACTS_DIR" && "$X86_64_SET" -eq 0 && "$AARCH64_SET" -eq 0 && "$ARMV7_SET" -eq 0 ) ]]; then
|
||||||
|
[[ -z "$ARTIFACTS_DIR" && "$X86_64_SET" -eq 0 && "$AARCH64_SET" -eq 0 && "$ARMV7_SET" -eq 0 ]] || \
|
||||||
|
fail "--release cannot be combined with local artifact input"
|
||||||
|
require_cmd curl
|
||||||
|
require_cmd python3
|
||||||
|
require_cmd dpkg-deb
|
||||||
|
GITHUB_RELEASE="${GITHUB_RELEASE:-latest}"
|
||||||
|
if [[ "$GITHUB_RELEASE" =~ ^https://github\.com/([^/]+/[^/]+)/releases/tag/([^/?#]+)/?$ ]]; then
|
||||||
|
GITHUB_REPO="${BASH_REMATCH[1]}"
|
||||||
|
GITHUB_RELEASE="${BASH_REMATCH[2]}"
|
||||||
|
fi
|
||||||
|
[[ "$GITHUB_REPO" =~ ^[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+$ ]] || fail "Invalid --github-repo"
|
||||||
|
for proxy in "$GITHUB_PROXY" "$GITHUB_API_PROXY"; do
|
||||||
|
[[ -z "$proxy" || "$proxy" =~ ^https?://[^[:space:]]+$ ]] || fail "Invalid GitHub proxy URL: $proxy"
|
||||||
|
done
|
||||||
|
release_path="latest"
|
||||||
|
if [[ "$GITHUB_RELEASE" != latest ]]; then
|
||||||
|
release_tag="$(python3 -c 'import sys,urllib.parse; print(urllib.parse.quote(urllib.parse.unquote(sys.argv[1]), safe=""))' "$GITHUB_RELEASE")"
|
||||||
|
release_path="tags/${release_tag}"
|
||||||
|
fi
|
||||||
|
release_json="${BUILD_DIR}/release.json"
|
||||||
|
download_github "https://api.github.com/repos/${GITHUB_REPO}/releases/${release_path}" "$release_json" "$GITHUB_API_PROXY"
|
||||||
|
python3 - "$release_json" "${BUILD_DIR}/assets.tsv" "${BUILD_DIR}/metadata.json" "$VERSION" <<'PY'
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
|
||||||
|
release = json.load(open(sys.argv[1], encoding="utf-8"))
|
||||||
|
assets = []
|
||||||
|
seen = set()
|
||||||
|
for asset in release["assets"]:
|
||||||
|
match = re.fullmatch(r"one-kvm_(\d+\.\d+\.\d+)_(amd64|arm64|armhf)\.deb", asset["name"])
|
||||||
|
if not match:
|
||||||
|
continue
|
||||||
|
version, arch = match.groups()
|
||||||
|
if arch in seen:
|
||||||
|
sys.exit(f"Error: Duplicate deb architecture: {arch}")
|
||||||
|
seen.add(arch)
|
||||||
|
url = asset["browser_download_url"]
|
||||||
|
if not url.startswith("https://github.com/") or any(c in url for c in "\t\r\n"):
|
||||||
|
sys.exit("Error: Invalid GitHub asset URL")
|
||||||
|
digest = asset.get("digest") or "-"
|
||||||
|
if digest != "-" and not re.fullmatch(r"sha256:[a-fA-F0-9]{64}", digest):
|
||||||
|
sys.exit("Error: Invalid GitHub asset digest")
|
||||||
|
assets.append((arch, version, url, digest, str(asset["size"])))
|
||||||
|
if not assets:
|
||||||
|
sys.exit("Error: Release has no supported One-KVM deb assets")
|
||||||
|
versions = {a[1] for a in assets}
|
||||||
|
if len(versions) != 1:
|
||||||
|
sys.exit("Error: Release deb versions differ")
|
||||||
|
version = versions.pop()
|
||||||
|
if sys.argv[4] and sys.argv[4] != version:
|
||||||
|
sys.exit(f"Error: --version {sys.argv[4]} differs from GitHub deb version {version}")
|
||||||
|
with open(sys.argv[2], "w", encoding="utf-8") as output:
|
||||||
|
for asset in assets:
|
||||||
|
output.write("\t".join(asset) + "\n")
|
||||||
|
with open(sys.argv[3], "w", encoding="utf-8") as output:
|
||||||
|
json.dump(dict(version=version, published_at=release["published_at"],
|
||||||
|
notes=release.get("body") or ""), output, ensure_ascii=False)
|
||||||
|
PY
|
||||||
|
VERSION="$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1]))["version"])' "${BUILD_DIR}/metadata.json")"
|
||||||
|
if [[ "$PUBLISHED_AT_SET" -eq 0 ]]; then
|
||||||
|
PUBLISHED_AT="$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1]))["published_at"])' "${BUILD_DIR}/metadata.json")"
|
||||||
|
fi
|
||||||
|
if [[ ${#NOTES[@]} -eq 0 ]]; then
|
||||||
|
mapfile -t NOTES < <(python3 -c 'import json,re,sys; body=json.load(open(sys.argv[1]))["notes"]; print("\n".join(re.sub(r"^[-*+]\s+", "", line.strip()) for line in body.splitlines() if line.strip()), end="")' "${BUILD_DIR}/metadata.json")
|
||||||
|
fi
|
||||||
|
ARTIFACTS_DIR="${BUILD_DIR}/binaries"
|
||||||
|
mkdir -p "$ARTIFACTS_DIR"
|
||||||
|
while IFS=$'\t' read -r arch deb_version url digest expected_size; do
|
||||||
|
case "$arch" in
|
||||||
|
amd64) triple="x86_64-unknown-linux-gnu" ;;
|
||||||
|
arm64) triple="aarch64-unknown-linux-gnu" ;;
|
||||||
|
armhf) triple="armv7-unknown-linux-gnueabihf" ;;
|
||||||
|
esac
|
||||||
|
deb="${BUILD_DIR}/one-kvm_${deb_version}_${arch}.deb"
|
||||||
|
download_github "$url" "$deb" "$GITHUB_PROXY"
|
||||||
|
[[ "$(stat -c%s "$deb")" == "$expected_size" ]] || fail "deb size mismatch: $arch"
|
||||||
|
if [[ "$digest" != - ]]; then
|
||||||
|
actual_sha="$(sha256sum "$deb" | awk '{print $1}')"
|
||||||
|
[[ "$actual_sha" == "${digest#sha256:}" ]] || fail "deb SHA256 mismatch: $arch"
|
||||||
|
fi
|
||||||
|
[[ "$(dpkg-deb -f "$deb" Package)" == one-kvm ]] || fail "Unexpected deb package: $arch"
|
||||||
|
[[ "$(dpkg-deb -f "$deb" Version)" == "$VERSION" ]] || fail "deb version mismatch: $arch"
|
||||||
|
[[ "$(dpkg-deb -f "$deb" Architecture)" == "$arch" ]] || fail "deb architecture mismatch: $arch"
|
||||||
|
extract_dir="${BUILD_DIR}/extract-${arch}"
|
||||||
|
dpkg-deb -x "$deb" "$extract_dir"
|
||||||
|
binary="${extract_dir}/usr/bin/one-kvm"
|
||||||
|
[[ -f "$binary" && ! -L "$binary" && -s "$binary" ]] || fail "deb missing /usr/bin/one-kvm: $arch"
|
||||||
|
cp "$binary" "${ARTIFACTS_DIR}/one-kvm-${triple}"
|
||||||
|
done < "${BUILD_DIR}/assets.tsv"
|
||||||
|
fi
|
||||||
|
|
||||||
[[ -n "$VERSION" ]] || fail "--version is required"
|
[[ -n "$VERSION" ]] || fail "--version is required"
|
||||||
is_valid_version "$VERSION" || fail "Invalid --version: ${VERSION} (expected x.x.x)"
|
is_valid_version "$VERSION" || fail "Invalid --version: ${VERSION} (expected x.x.x)"
|
||||||
is_valid_channel "$RELEASE_CHANNEL" || fail "Invalid --release-channel: ${RELEASE_CHANNEL}"
|
is_valid_channel "$RELEASE_CHANNEL" || fail "Invalid --release-channel: ${RELEASE_CHANNEL}"
|
||||||
@@ -220,9 +381,6 @@ if [[ ${#SRC_BY_TRIPLE[@]} -eq 0 ]]; then
|
|||||||
fail "No artifact found. Provide --artifacts-dir or at least one of --x86_64/--aarch64/--armv7."
|
fail "No artifact found. Provide --artifacts-dir or at least one of --x86_64/--aarch64/--armv7."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
BUILD_DIR="$(mktemp -d)"
|
|
||||||
trap 'rm -rf "$BUILD_DIR"' EXIT
|
|
||||||
|
|
||||||
SITE_DIR="${BUILD_DIR}/${SITE_NAME}"
|
SITE_DIR="${BUILD_DIR}/${SITE_NAME}"
|
||||||
V1_DIR="${SITE_DIR}/v1"
|
V1_DIR="${SITE_DIR}/v1"
|
||||||
BIN_DIR="${V1_DIR}/bin/${VERSION}"
|
BIN_DIR="${V1_DIR}/bin/${VERSION}"
|
||||||
@@ -268,7 +426,7 @@ RELEASES_FILE="${V1_DIR}/releases.json"
|
|||||||
echo ' {'
|
echo ' {'
|
||||||
echo " \"version\": \"${VERSION}\","
|
echo " \"version\": \"${VERSION}\","
|
||||||
echo " \"channel\": \"${RELEASE_CHANNEL}\","
|
echo " \"channel\": \"${RELEASE_CHANNEL}\","
|
||||||
echo " \"published_at\": \"${PUBLISHED_AT}\","
|
echo " \"published_at\": \"$(json_escape "$PUBLISHED_AT")\","
|
||||||
|
|
||||||
if [[ ${#NOTES[@]} -eq 0 ]]; then
|
if [[ ${#NOTES[@]} -eq 0 ]]; then
|
||||||
echo ' "notes": [],'
|
echo ' "notes": [],'
|
||||||
|
|||||||
Reference in New Issue
Block a user