feat: OTA 附件改用 7z 压缩包(体积 -73%),客户端纯 Rust 解压
- 发布脚本:附件打包为 <binary>.7z(-mx=9)+ .7z.sha256 上传; 同时保留裸二进制附件,v1.0.3 及更早客户端(不认识 7z)仍可升级 - update.rs:pick_assets 优先选 7z、无则回退裸二进制; 下载 → 大小对账 + sha256 校验 → sevenz-rust 解压出二进制暂存 (压缩包随即删除);暂存目录清理排除 .7z - UpdateInfo 增加 asset_name 字段区分压缩包/裸文件 - 实测 21.3MB exe → 5.8MB(7-Zip -mx=9)
This commit is contained in:
Generated
+83
-5
@@ -632,15 +632,30 @@ dependencies = [
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bit-set"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0481a0e032742109b1133a095184ee93d88f3dc9e0d28a5d033dc77a073f44f"
|
||||
dependencies = [
|
||||
"bit-vec 0.7.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bit-set"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34ddef2995421ab6a5c779542c81ee77c115206f4ad9d5a8e05f4ff49716a3dd"
|
||||
dependencies = [
|
||||
"bit-vec",
|
||||
"bit-vec 0.9.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bit-vec"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2c54ff287cfc0a34f38a6b832ea1bd8e448a330b3e40a50859e6488bee07f22"
|
||||
|
||||
[[package]]
|
||||
name = "bit-vec"
|
||||
version = "0.9.1"
|
||||
@@ -1308,6 +1323,21 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc"
|
||||
version = "3.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d"
|
||||
dependencies = [
|
||||
"crc-catalog",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc-catalog"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853"
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.5.2"
|
||||
@@ -1827,6 +1857,17 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "filetime_creation"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c25b5d475550e559de5b0c0084761c65325444e3b6c9e298af9cefe7a9ef3a5f"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"filetime",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.14"
|
||||
@@ -3926,6 +3967,15 @@ dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lzma-rust"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5baab2bbbd7d75a144d671e9ff79270e903957d92fb7386fd39034c709bd2661"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mac"
|
||||
version = "0.1.1"
|
||||
@@ -4153,6 +4203,7 @@ dependencies = [
|
||||
"sentry",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sevenz-rust",
|
||||
"sha1",
|
||||
"sha2 0.10.9",
|
||||
"smol",
|
||||
@@ -4175,7 +4226,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b2bf919621e7975acb27d881bae2fb993e0d45c8e0446e85e6272971e00dc8df"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"bit-set",
|
||||
"bit-set 0.9.1",
|
||||
"bitflags 2.13.2",
|
||||
"cfg-if",
|
||||
"cfg_aliases",
|
||||
@@ -4312,6 +4363,16 @@ dependencies = [
|
||||
"instant",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nt-time"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2de419e64947cd8830e66beb584acc3fb42ed411d103e3c794dda355d1b374b5"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ntapi"
|
||||
version = "0.4.3"
|
||||
@@ -6577,6 +6638,23 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sevenz-rust"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26482cf1ecce4540dc782fc70019eba89ffc4d87b3717eb5ec524b5db6fdefef"
|
||||
dependencies = [
|
||||
"bit-set 0.6.0",
|
||||
"byteorder",
|
||||
"crc",
|
||||
"filetime_creation",
|
||||
"js-sys",
|
||||
"lzma-rust",
|
||||
"nt-time",
|
||||
"sha2 0.10.9",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha1"
|
||||
version = "0.10.7"
|
||||
@@ -8250,8 +8328,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f519832254e56965a9940c4af57dcb75f702b6f6fa4a0b172f685395843a4d7"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"bit-set",
|
||||
"bit-vec",
|
||||
"bit-set 0.9.1",
|
||||
"bit-vec 0.9.1",
|
||||
"bitflags 2.13.2",
|
||||
"bytemuck",
|
||||
"cfg_aliases",
|
||||
@@ -8322,7 +8400,7 @@ dependencies = [
|
||||
"android_system_properties",
|
||||
"arrayvec",
|
||||
"ash",
|
||||
"bit-set",
|
||||
"bit-set 0.9.1",
|
||||
"bitflags 2.13.2",
|
||||
"block2 0.6.2",
|
||||
"bytemuck",
|
||||
|
||||
@@ -43,6 +43,8 @@ rfd = "0.17"
|
||||
# OTA 自更新:Gitea Release 检查/下载(ureq 用 rustls,与 rumqttc 依赖同源)
|
||||
ureq = { version = "2", features = ["json"] }
|
||||
sha2 = "0.10"
|
||||
# Release 附件为 7z 压缩包,纯 Rust 解压(客户端免装 7-Zip)
|
||||
sevenz-rust = "0.6"
|
||||
|
||||
# 错误上报(自建 Sentry):panic 捕获 + 持久化错误;rustls 传输避免 openssl 依赖
|
||||
sentry = { version = "0.49", default-features = false, features = [
|
||||
|
||||
+90
-19
@@ -23,6 +23,8 @@ pub struct UpdateInfo {
|
||||
pub version: String,
|
||||
/// Release 说明(Markdown 原文)
|
||||
pub notes: String,
|
||||
/// 附件名(判断 7z 压缩包 / 裸二进制)
|
||||
pub asset_name: String,
|
||||
/// 平台对应产物下载地址
|
||||
pub asset_url: String,
|
||||
/// sha256 校验文件地址(若 Release 提供了 `.sha256` 侧车)
|
||||
@@ -52,12 +54,17 @@ pub fn platform_triple() -> &'static str {
|
||||
}
|
||||
}
|
||||
|
||||
/// 某版本的产物文件名(与 CI 的命名约定 `mqttx-<ver>-<triple>-mqttx[.exe]` 一致)。
|
||||
pub fn asset_name_for(version: &str) -> String {
|
||||
/// 某版本解压后二进制的暂存名(与 CI 的命名约定 `mqttx-v<ver>-<triple>-mqttx[.exe]` 一致)。
|
||||
pub fn binary_name_for(version: &str) -> String {
|
||||
let ext = if cfg!(windows) { ".exe" } else { "" };
|
||||
format!("mqttx-v{}-{}-mqttx{}", version, platform_triple(), ext)
|
||||
}
|
||||
|
||||
/// 某版本的 7z 压缩附件名(打包端与此约定一致;内部为 [`binary_name_for`])。
|
||||
pub fn asset_name_for(version: &str) -> String {
|
||||
format!("{}.7z", binary_name_for(version))
|
||||
}
|
||||
|
||||
fn agent() -> ureq::Agent {
|
||||
ureq::AgentBuilder::new()
|
||||
// 整体上限:防止慢速滴流的下载无限占用阻塞线程
|
||||
@@ -108,15 +115,28 @@ pub fn is_newer(a: &str, b: &str) -> bool {
|
||||
}
|
||||
|
||||
/// 从 Release JSON 里挑选当前平台的产物与 sha256 侧车。
|
||||
/// 优先 7z 压缩包(体积小、下载快);Release 未提供 7z 时回退裸二进制
|
||||
/// (兼容旧发布方式)。
|
||||
pub fn pick_assets(rel: &serde_json::Value) -> Option<(String, String, u64, Option<String>)> {
|
||||
let assets = rel.get("assets")?.as_array()?;
|
||||
let want_ext = if cfg!(windows) { ".exe" } else { "" };
|
||||
let triple = platform_triple();
|
||||
|
||||
// (后缀过滤器, 说明):第一轮 7z,第二轮裸二进制
|
||||
for suffix in [".7z", want_ext] {
|
||||
for a in assets {
|
||||
let Some(name) = a.get("name").and_then(|n| n.as_str()) else {
|
||||
continue;
|
||||
};
|
||||
if !name.contains(triple) || !name.ends_with(want_ext) || name.ends_with(".sha256") {
|
||||
if !name.contains(triple)
|
||||
|| !name.ends_with(suffix)
|
||||
|| name.ends_with(".sha256")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
// 7z 轮要排除裸二进制(windows 裸 exe 不以 .7z 结尾,天然互斥)
|
||||
if suffix == ".7z" && !name.ends_with(&format!("{want_ext}.7z")) && !want_ext.is_empty()
|
||||
{
|
||||
continue;
|
||||
}
|
||||
let Some(url) = a.get("browser_download_url").and_then(|u| u.as_str()) else {
|
||||
@@ -135,6 +155,7 @@ pub fn pick_assets(rel: &serde_json::Value) -> Option<(String, String, u64, Opti
|
||||
.map(|s| s.to_string());
|
||||
return Some((name.to_string(), url, size, sha));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
@@ -164,7 +185,7 @@ pub fn check_latest() -> Result<Option<UpdateInfo>, String> {
|
||||
if !is_newer(tag, current_version()) {
|
||||
return Ok(None);
|
||||
}
|
||||
let Some((_, url, size, sha)) = pick_assets(&rel) else {
|
||||
let Some((name, url, size, sha)) = pick_assets(&rel) else {
|
||||
return Err(format!("Release {tag} 没有当前平台({})的产物", platform_triple()));
|
||||
};
|
||||
Ok(Some(UpdateInfo {
|
||||
@@ -174,6 +195,7 @@ pub fn check_latest() -> Result<Option<UpdateInfo>, String> {
|
||||
.and_then(|b| b.as_str())
|
||||
.unwrap_or_default()
|
||||
.to_string(),
|
||||
asset_name: name,
|
||||
asset_url: url,
|
||||
sha256_url: sha,
|
||||
size,
|
||||
@@ -214,13 +236,21 @@ pub fn check_and_download() -> Result<Option<StagedUpdate>, String> {
|
||||
|
||||
/// 下载并校验到暂存区,返回待安装的更新。
|
||||
///
|
||||
/// 附件为 7z 压缩包:下载 → 校验 → 解压出二进制暂存(压缩包随即删除)。
|
||||
/// 回退路径(裸二进制附件)直接校验暂存。
|
||||
/// `on_progress(downloaded, total)` 在阻塞线程上回调,total 为 0 表示未知。
|
||||
pub fn download_and_stage(
|
||||
info: &UpdateInfo,
|
||||
on_progress: &dyn Fn(u64, u64),
|
||||
) -> Result<StagedUpdate, String> {
|
||||
let dir = stage_dir()?;
|
||||
let final_name = asset_name_for(&info.version);
|
||||
let binary_name = binary_name_for(&info.version);
|
||||
let is_7z = info.asset_name.ends_with(".7z");
|
||||
let final_name = if is_7z {
|
||||
asset_name_for(&info.version)
|
||||
} else {
|
||||
binary_name.clone()
|
||||
};
|
||||
// tmp 名加入毫秒时间戳:同 pid 并发下载不共用文件
|
||||
let tmp = dir.join(format!(
|
||||
"{}.{}.{}.tmp",
|
||||
@@ -240,11 +270,31 @@ pub fn download_and_stage(
|
||||
}
|
||||
|
||||
// 清掉其他版本的残留暂存文件,避免堆积与误装旧版
|
||||
let staged_path = if is_7z {
|
||||
// 解压到暂存目录,取出二进制后删掉压缩包
|
||||
if let Err(e) = sevenz_rust::decompress_file(&tmp, &dir) {
|
||||
let _ = std::fs::remove_file(&tmp);
|
||||
return Err(format!("解压更新包失败: {e}"));
|
||||
}
|
||||
let _ = std::fs::remove_file(&tmp);
|
||||
let extracted = dir.join(&binary_name);
|
||||
if !extracted.exists() {
|
||||
return Err(format!(
|
||||
"更新包内缺少 {},压缩包内容异常",
|
||||
binary_name
|
||||
));
|
||||
}
|
||||
// 确认解压产物后再清理其他版本,最后返回二进制路径
|
||||
remove_other_staged(&dir, &extracted);
|
||||
extracted
|
||||
} else {
|
||||
remove_other_staged(&dir, &dst);
|
||||
std::fs::rename(&tmp, &dst).map_err(|e| format!("暂存失败: {e}"))?;
|
||||
dst
|
||||
};
|
||||
Ok(StagedUpdate {
|
||||
version: info.version.clone(),
|
||||
path: dst,
|
||||
path: staged_path,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -307,6 +357,7 @@ fn remove_other_staged(dir: &std::path::Path, keep: &std::path::Path) {
|
||||
&& name.contains(platform_triple())
|
||||
&& !name.ends_with(".tmp")
|
||||
&& !name.ends_with(".sha256")
|
||||
&& !name.ends_with(".7z")
|
||||
{
|
||||
let _ = std::fs::remove_file(&p);
|
||||
}
|
||||
@@ -538,36 +589,56 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn asset_naming_matches_ci_convention() {
|
||||
let ext = if cfg!(windows) { ".exe" } else { "" };
|
||||
// 附件为 7z 压缩包;解压出的二进制保留原命名
|
||||
assert_eq!(
|
||||
asset_name_for("1.0.1"),
|
||||
format!("mqttx-v1.0.1-{}-mqttx{}", platform_triple(), if cfg!(windows) { ".exe" } else { "" })
|
||||
format!("mqttx-v1.0.1-{triple}-mqttx{ext}.7z", triple = platform_triple())
|
||||
);
|
||||
assert_eq!(
|
||||
binary_name_for("1.0.1"),
|
||||
format!("mqttx-v1.0.1-{triple}-mqttx{ext}", triple = platform_triple())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pick_assets_selects_platform_and_sidecar() {
|
||||
fn pick_assets_prefers_7z_over_raw_binary() {
|
||||
let rel: serde_json::Value = serde_json::json!({
|
||||
"tag_name": "v1.0.1",
|
||||
"assets": [
|
||||
{"name": "mqttx-v1.0.1-x86_64-pc-windows-msvc-mqttx.exe", "size": 21000000,
|
||||
"browser_download_url": "https://x/win.exe"},
|
||||
{"name": "mqttx-v1.0.1-x86_64-pc-windows-msvc-mqttx.exe.sha256", "size": 65,
|
||||
"browser_download_url": "https://x/win.exe.sha256"},
|
||||
{"name": "mqttx-v1.0.1-x86_64-pc-windows-msvc-mqttx.exe.7z", "size": 9000000,
|
||||
"browser_download_url": "https://x/win.7z"},
|
||||
{"name": "mqttx-v1.0.1-x86_64-pc-windows-msvc-mqttx.exe.7z.sha256", "size": 65,
|
||||
"browser_download_url": "https://x/win.7z.sha256"}
|
||||
]
|
||||
});
|
||||
let (name, url, size, sha) = pick_assets(&rel).expect("应选中 7z");
|
||||
assert!(name.ends_with(".7z"), "应优先 7z 压缩包,实际 {name}");
|
||||
assert_eq!(url, "https://x/win.7z");
|
||||
assert_eq!(size, 9000000);
|
||||
assert_eq!(sha.as_deref(), Some("https://x/win.7z.sha256"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pick_assets_falls_back_to_raw_binary() {
|
||||
// 旧发布方式只有裸二进制:仍可被选中
|
||||
let rel: serde_json::Value = serde_json::json!({
|
||||
"tag_name": "v1.0.1",
|
||||
"assets": [
|
||||
{"name": "mqttx-v1.0.1-x86_64-unknown-linux-gnu-mqttx", "size": 1,
|
||||
"browser_download_url": "https://x/linux"},
|
||||
{"name": "mqttx-v1.0.1-x86_64-pc-windows-msvc-mqttx.exe", "size": 19132928,
|
||||
"browser_download_url": "https://x/win"},
|
||||
{"name": "mqttx-v1.0.1-x86_64-pc-windows-msvc-mqttx.exe.sha256", "size": 65,
|
||||
"browser_download_url": "https://x/win.sha256"}
|
||||
]
|
||||
});
|
||||
let (name, url, size, sha) = pick_assets(&rel).expect("应选中平台产物");
|
||||
if cfg!(windows) {
|
||||
assert_eq!(name, "mqttx-v1.0.1-x86_64-pc-windows-msvc-mqttx.exe");
|
||||
let (name, url, _size, sha) = pick_assets(&rel).expect("应回退裸二进制");
|
||||
assert!(name.ends_with(".exe"));
|
||||
assert_eq!(url, "https://x/win");
|
||||
assert_eq!(size, 19132928);
|
||||
assert_eq!(sha.as_deref(), Some("https://x/win.sha256"));
|
||||
} else {
|
||||
assert_eq!(name, "mqttx-v1.0.1-x86_64-unknown-linux-gnu-mqttx");
|
||||
assert_eq!(url, "https://x/linux");
|
||||
assert_eq!(sha, None);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -237,6 +237,41 @@ foreach ($f in $Files) {
|
||||
if ($name -match "^mqttx-v.*-mqttx(\.exe)?$") { $asset = $name }
|
||||
elseif ($name.EndsWith(".exe")) { $asset = "mqttx-v$VerNum-$Triple-mqttx.exe" }
|
||||
else { $asset = "mqttx-v$VerNum-$Triple-mqttx" }
|
||||
|
||||
# ── 7z 压缩附件(OTA 主通道:体积小、下载快)──
|
||||
# 内部文件名 = $asset(应用解压后按此名暂存安装)
|
||||
$SevenZip = @("$env:ProgramFiles\7-Zip\7z.exe", "${env:ProgramFiles(x86)}\7-Zip\7z.exe") |
|
||||
Where-Object { Test-Path $_ } | Select-Object -First 1
|
||||
if (-not $SevenZip) { $SevenZip = (Get-Command 7z -ErrorAction SilentlyContinue).Source }
|
||||
if ($SevenZip) {
|
||||
$packDir = Join-Path $env:TEMP ("mqttx-pack-" + [guid]::NewGuid().ToString("N"))
|
||||
New-Item -ItemType Directory -Path $packDir | Out-Null
|
||||
Copy-Item $f (Join-Path $packDir $asset)
|
||||
$archive = Join-Path $env:TEMP "$asset.7z"
|
||||
& $SevenZip a -y -mx=9 $archive (Join-Path $packDir $asset) | Out-Null
|
||||
if ($LASTEXITCODE -eq 0 -and (Test-Path $archive)) {
|
||||
$asum = (Get-FileHash -Algorithm SHA256 $archive).Hash.ToLower()
|
||||
"$asum $asset.7z" | Set-Content -NoNewline -Encoding ascii "$archive.sha256"
|
||||
foreach ($a in @("$asset.7z", "$asset.7z.sha256")) {
|
||||
$code = Upload-ReleaseAsset "$Api/releases/$releaseId/assets?name=$a" (Join-Path $env:TEMP $a)
|
||||
if ($code -eq 201) {
|
||||
Write-Host " ✓ Release 附件 $a" -ForegroundColor Green
|
||||
}
|
||||
else {
|
||||
Write-Host " ! Release 附件 $a 上传失败 (HTTP $code)" -ForegroundColor Yellow
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
Write-Host " ! 7z 打包失败(exit=$LASTEXITCODE),跳过压缩附件" -ForegroundColor Yellow
|
||||
}
|
||||
Remove-Item $packDir, $archive, "$archive.sha256" -Recurse -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
else {
|
||||
Write-Host " ! 未找到 7-Zip,跳过 7z 附件(仅上传裸二进制)" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
# ── 裸二进制附件:v1.0.3 及更早客户端不认识 7z,需要它升级 ──
|
||||
$tmp = Join-Path $env:TEMP $asset
|
||||
Copy-Item $f $tmp -Force
|
||||
$sum = (Get-FileHash -Algorithm SHA256 $f).Hash.ToLower()
|
||||
|
||||
@@ -193,6 +193,37 @@ for f in "${FILES[@]}"; do
|
||||
*.exe) asset="mqttx-v${VER_NUM}-${TRIPLE}-mqttx.exe" ;;
|
||||
*) asset="mqttx-v${VER_NUM}-${TRIPLE}-mqttx" ;;
|
||||
esac
|
||||
|
||||
# ── 7z 压缩附件(OTA 主通道):内部文件名 = $asset(应用解压后按此名安装)──
|
||||
SEVENZIP="$(command -v 7z 7za 2>/dev/null | head -1)"
|
||||
[ -z "$SEVENZIP" ] && [ -x "/c/Program Files/7-Zip/7z.exe" ] && SEVENZIP="/c/Program Files/7-Zip/7z.exe"
|
||||
if [ -n "$SEVENZIP" ]; then
|
||||
"$SEVENZIP" a -y -mx=9 "$STAGE/$asset.7z" "$f" >/dev/null
|
||||
# 归档内的文件名需为 $asset:7z 存的是源文件名,先复制改名再压
|
||||
rm -f "$STAGE/$asset.7z"
|
||||
cp -f "$f" "$STAGE/$asset"
|
||||
"$SEVENZIP" a -y -mx=9 "$STAGE/$asset.7z" "$STAGE/$asset" >/dev/null
|
||||
if [ -f "$STAGE/$asset.7z" ]; then
|
||||
printf '%s %s\n' "$(sha256 "$STAGE/$asset.7z")" "$asset.7z" > "$STAGE/$asset.7z.sha256"
|
||||
for a in "$asset.7z" "$asset.7z.sha256"; do
|
||||
code="$(curl -sS -o "$STAGE/up.json" -w '%{http_code}' \
|
||||
-X POST \
|
||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||
-F "attachment=@${STAGE}/${a}" \
|
||||
"${API}/releases/${RELEASE_ID}/assets?name=${a}")"
|
||||
case "$code" in
|
||||
201) echo " ✓ Release 附件 $a" ;;
|
||||
*) echo " ! Release 附件 $a 上传失败 (HTTP $code)" >&2; cat "$STAGE/up.json" >&2 || true ;;
|
||||
esac
|
||||
done
|
||||
else
|
||||
echo " ! 7z 打包失败,跳过压缩附件" >&2
|
||||
fi
|
||||
else
|
||||
echo " ! 未找到 7z/7za,跳过 7z 附件(仅上传裸二进制)" >&2
|
||||
fi
|
||||
|
||||
# ── 裸二进制附件:v1.0.3 及更早客户端不认识 7z,需要它升级 ──
|
||||
cp -f "$f" "$STAGE/$asset"
|
||||
printf '%s %s\n' "$(sha256 "$f")" "$asset" > "$STAGE/$asset.sha256"
|
||||
for a in "$asset" "$asset.sha256"; do
|
||||
|
||||
Reference in New Issue
Block a user