feat: 全量切换为完整安装包流式下载、彻底废除增量差分包、全流程实时进度可见
TallyNote release / linux-x64 (push) Successful in 7m51s

This commit is contained in:
Qiufeng
2026-09-04 22:58:24 +08:00
parent 05a679c2c8
commit fa2fd94579
5 changed files with 8 additions and 27 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "tallynote", "name": "tallynote",
"version": "1.1.41", "version": "1.1.42",
"private": true, "private": true,
"type": "module", "type": "module",
"packageManager": "pnpm@9.0.6", "packageManager": "pnpm@9.0.6",
+2 -19
View File
@@ -51,25 +51,8 @@ mkdir -p "$OUT_DIR"
archive="$OUT_DIR/tallynote-${VERSION}-linux-${ARCH}-${LIBC}.tar.gz" archive="$OUT_DIR/tallynote-${VERSION}-linux-${ARCH}-${LIBC}.tar.gz"
tar -C "$stage" -czf "$archive" --owner=0 --group=0 --numeric-owner . tar -C "$stage" -czf "$archive" --owner=0 --group=0 --numeric-owner .
# The application-only asset is used by the online updater. It deliberately # Always produce only the complete full standalone release package so users get a clean,
# excludes the stable runtime (Node and production dependencies), systemd # transparent streaming download with all dependencies pre-packaged.
# helpers and installer files; the updater overlays it on the currently
# installed, already-validated runtime before atomically switching releases.
app_stage=$(mktemp -d)
trap 'rm -rf "$stage" "$app_stage"' EXIT
mkdir -p "$app_stage/dist" "$app_stage/migrations" "$app_stage/bin" "$app_stage/scripts" "$app_stage/systemd"
cp -a "$stage/dist/server" "$app_stage/dist/"
cp -a "$stage/dist/shared" "$app_stage/dist/"
cp -a "$stage/dist/web" "$app_stage/dist/"
cp -a "$stage/migrations/." "$app_stage/migrations/"
cp -a "$stage/bin/." "$app_stage/bin/"
cp -a "$stage/scripts/." "$app_stage/scripts/"
cp -a "$stage/systemd/." "$app_stage/systemd/"
cp "$stage/package.json" "$app_stage/package.json"
cp "$stage/uninstall.sh" "$app_stage/uninstall.sh"
runtime_hash=$(sha256sum pnpm-lock.yaml | awk '{print $1}')
app_archive="$OUT_DIR/tallynote-${VERSION}-linux-${ARCH}-${LIBC}.update-${runtime_hash}.tar.gz"
tar -C "$app_stage" -czf "$app_archive" --owner=0 --group=0 --numeric-owner .
# Keep the sidecar useful when a caller builds more than one architecture into # Keep the sidecar useful when a caller builds more than one architecture into
# the same directory. The publishing script recomputes this list immediately # the same directory. The publishing script recomputes this list immediately
# before signing, so stale or hand-edited entries can never reach a Release. # before signing, so stale or hand-edited entries can never reach a Release.
+2 -1
View File
@@ -320,7 +320,8 @@ export async function checkForUpdate(database: Database.Database, config: AppCon
// Legacy or source installations may not contain the lockfile. They stay // Legacy or source installations may not contain the lockfile. They stay
// on the full release asset instead of risking an incompatible runtime. // on the full release asset instead of risking an incompatible runtime.
} }
let asset = selectReleaseAsset(metadata, platform, runtimeHash); // Force choosing the full standalone archive so users always get a real, visible streaming download
let asset = selectReleaseAsset(metadata, platform, undefined);
let signatureVerified = false; let signatureVerified = false;
if (asset) { if (asset) {
const integrity = await attachSidecarHash(metadata, asset, { const integrity = await attachSidecarHash(metadata, asset, {
-3
View File
@@ -413,9 +413,6 @@ export function selectReleaseAsset(release: ReleaseMetadata, platform = detectPl
const applicationUpdate = candidates.find((asset) => applicationUpdateRuntimeHash(asset.name) === normalizedRuntimeHash); const applicationUpdate = candidates.find((asset) => applicationUpdateRuntimeHash(asset.name) === normalizedRuntimeHash);
if (applicationUpdate) return applicationUpdate; if (applicationUpdate) return applicationUpdate;
} }
// Older clients choose the first matching asset. Releases therefore keep
// the traditional full archive first, while current clients explicitly
// opt into a compatible application-only asset.
return candidates.find((asset) => !applicationUpdateRuntimeHash(asset.name)); return candidates.find((asset) => !applicationUpdateRuntimeHash(asset.name));
} }
+3 -3
View File
@@ -877,7 +877,7 @@ export default function UpdatePage({
{/* 场景 A: 实时下载中态 (Exact ASCII) */} {/* 场景 A: 实时下载中态 (Exact ASCII) */}
{job?.status === "downloading" && ( {job?.status === "downloading" && (
<div className="tn-modal-card-box"> <div className="tn-modal-card-box">
<div className="tn-modal-card-title">正在从官方源直接流式拉取更新包...</div> <div className="tn-modal-card-title">正在从官方源流式下载完整安装包 ({job.sizeBytes ? bytesText(job.sizeBytes) : "115 MB"})...</div>
<div <div
className="tn-progress-stream" className="tn-progress-stream"
@@ -903,7 +903,7 @@ export default function UpdatePage({
</div> </div>
<div className="tn-modal-info-note"> <div className="tn-modal-info-note">
[i] 更新包由应用进程直接流式拉取并自动比对 SHA-256 校验和,0 秒秒级启动,无需等待外部系统守护进程调度。 [i] 正在流式拉取全量生产包(含运行环境与全部依赖),进度实时更新,不影响当前前台记账操作。
</div> </div>
<div className="tn-modal-actions-bar"> <div className="tn-modal-actions-bar">
@@ -950,7 +950,7 @@ export default function UpdatePage({
<div className="tn-modal-card-box"> <div className="tn-modal-card-box">
<div className="tn-modal-card-title" style={{ color: "#2f7d5c", display: "flex", alignItems: "center", gap: 8 }}> <div className="tn-modal-card-title" style={{ color: "#2f7d5c", display: "flex", alignItems: "center", gap: 8 }}>
<CheckCircle2 size={18} /> <CheckCircle2 size={18} />
更新包下载完成,SHA-256 指纹与 Ed25519 数字签名校验无误,准备就绪。 全量安装包下载与校验完成,SHA-256 指纹核对无误,准备就绪!
</div> </div>
<div className="tn-upgrade-safety-tips" style={{ margin: "14px 0" }}> <div className="tn-upgrade-safety-tips" style={{ margin: "14px 0" }}>
<div>• 点击立即应用后,系统将自动创建数据库与附件的完整快照备份;</div> <div>• 点击立即应用后,系统将自动创建数据库与附件的完整快照备份;</div>