release: 1.3.4
This commit is contained in:
+11
-30
@@ -1,5 +1,5 @@
|
||||
import { lstatSync, realpathSync, readFileSync, unlinkSync } from "node:fs";
|
||||
import { chmod, mkdir, mkdtemp, rename, rm, writeFile } from "node:fs/promises";
|
||||
import { chmod, lstat, mkdir, mkdtemp, rename, rm, writeFile } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { createPublicKey, randomUUID, verify as verifySignature } from "node:crypto";
|
||||
import type Database from "better-sqlite3";
|
||||
@@ -16,7 +16,6 @@ import {
|
||||
isNewerVersion,
|
||||
normalizeReleasePermissions,
|
||||
parseSemver,
|
||||
runtimeHashFromLockfile,
|
||||
sanitizeAssetName,
|
||||
selectReleaseAsset,
|
||||
validateHttpsUrl,
|
||||
@@ -211,14 +210,9 @@ export async function checkForUpdate(database: Database.Database, config: AppCon
|
||||
} catch {
|
||||
throw new AppError(502, "UPDATE_CHECK_FAILED", "暂时无法获取最新版本,请稍后重试");
|
||||
}
|
||||
let runtimeHash: string | undefined;
|
||||
try {
|
||||
runtimeHash = runtimeHashFromLockfile(readFileSync(path.join(config.projectRoot, "pnpm-lock.yaml")));
|
||||
} catch {
|
||||
// Legacy or source installations may not contain the lockfile. They stay
|
||||
// on the full release asset instead of risking an incompatible runtime.
|
||||
}
|
||||
// Force choosing the full standalone archive so users always get a real, visible streaming download
|
||||
// Always select the complete production archive. The host Node.js runtime
|
||||
// is reused, while the application package remains self-contained and
|
||||
// identical for first installs and in-place updates.
|
||||
let asset = selectReleaseAsset(metadata, platform, undefined);
|
||||
let signatureVerified = false;
|
||||
if (asset) {
|
||||
@@ -688,8 +682,9 @@ export function cancelUpdateJob(
|
||||
* The root runner only needs to apply (stop/backup/switch/restart) afterwards.
|
||||
*
|
||||
* This function runs asynchronously outside the request lifecycle. It updates
|
||||
* the job row in the database so the frontend can poll progress. On success it
|
||||
* writes an apply request file so the systemd path unit triggers the runner.
|
||||
* the job row in the database so the frontend can poll progress. A successful
|
||||
* download only becomes staged; applying it is a separate, explicit action
|
||||
* that the administrator submits after reviewing the verification result.
|
||||
*/
|
||||
export async function downloadAndStageUpdate(
|
||||
database: Database.Database,
|
||||
@@ -756,8 +751,10 @@ export async function downloadAndStageUpdate(
|
||||
await extractSafeArchive(archivePath, payloadDir);
|
||||
await normalizeReleasePermissions(payloadDir);
|
||||
|
||||
const embeddedRuntime = await lstat(path.join(payloadDir, "runtime")).catch(() => null);
|
||||
if (embeddedRuntime) throw new Error("发布包不应包含 Node.js runtime");
|
||||
|
||||
// Verify payload contains dist directory
|
||||
const { lstat } = await import("node:fs/promises");
|
||||
const payloadInfo = await lstat(path.join(payloadDir, "dist")).catch(() => null);
|
||||
if (!payloadInfo?.isDirectory() || payloadInfo.isSymbolicLink()) {
|
||||
throw new Error("发布包缺少 dist 目录");
|
||||
@@ -765,26 +762,10 @@ export async function downloadAndStageUpdate(
|
||||
|
||||
// Transition to staged
|
||||
const staged = database.prepare(
|
||||
"UPDATE update_jobs SET status='staged', operation='apply', actual_sha256=?, size_bytes=?, download_path=?, updated_at=? WHERE id=? AND status IN ('verifying', 'downloading')",
|
||||
"UPDATE update_jobs SET status='staged', operation='download', actual_sha256=?, size_bytes=?, download_path=?, updated_at=? WHERE id=? AND status IN ('verifying', 'downloading')",
|
||||
).run(downloaded.sha256, downloaded.size, workspace, Date.now(), jobId);
|
||||
if (staged.changes !== 1) return; // cancelled
|
||||
|
||||
// Write apply request file for the root runner
|
||||
await writeUpdateRequest(config, {
|
||||
jobId,
|
||||
operation: "apply",
|
||||
version,
|
||||
metadataUrl,
|
||||
assetUrl,
|
||||
assetName,
|
||||
expectedSha256,
|
||||
requestedAt: Date.now(),
|
||||
currentLink: config.currentLink,
|
||||
releasesDir: config.releasesDir,
|
||||
dataDir: config.dataDir,
|
||||
stagedPath: workspace,
|
||||
});
|
||||
|
||||
writeAudit(database, {
|
||||
requestId: `download:${jobId}`,
|
||||
actorAdminId: adminId,
|
||||
|
||||
Reference in New Issue
Block a user