Files
edgeever/scripts/verify-linux-update-release.mjs
T

71 lines
3.1 KiB
JavaScript

import { createHash } from "node:crypto";
import { readFileSync, statSync } from "node:fs";
import { basename, join, resolve } from "node:path";
import { fileURLToPath } from "node:url";
const STABLE_VERSION = /^\d+\.\d+\.\d+$/;
const escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const hashFile = (path, algorithm, encoding) =>
createHash(algorithm).update(readFileSync(path)).digest(encoding);
export const verifyLinuxUpdateRelease = ({ directory, expectedVersion }) => {
const metadataPath = join(directory, "latest-linux.yml");
const metadata = readFileSync(metadataPath, "utf8");
const version = metadata.match(/^version:\s*([^\s]+)\s*$/m)?.[1];
if (!version || !STABLE_VERSION.test(version)) {
throw new Error("latest-linux.yml must contain a stable X.Y.Z version");
}
if (expectedVersion && version !== expectedVersion) {
throw new Error(`Linux update version ${version} does not match ${expectedVersion}`);
}
const appImageName = `EdgeEver-${version}-linux-x64.AppImage`;
const appImagePath = join(directory, appImageName);
const stats = statSync(appImagePath);
if (!stats.isFile() || stats.size <= 0) {
throw new Error(`Linux AppImage is missing or empty: ${appImagePath}`);
}
const sha512 = hashFile(appImagePath, "sha512", "base64");
const sha256 = hashFile(appImagePath, "sha256", "hex");
const escapedName = escapeRegExp(appImageName);
const updateUrls = [...metadata.matchAll(/^ - url:\s*(.+)\s*$/gm)].map((match) => match[1]);
if (updateUrls.length !== 1 || updateUrls[0] !== appImageName) {
throw new Error("latest-linux.yml must select exactly one expected AppImage");
}
const fileEntry = new RegExp(
`^ - url: ${escapedName}\\n sha512: ${escapeRegExp(sha512)}\\n size: ${stats.size}\\n blockMapSize: [1-9]\\d*$`,
"m",
);
if (!fileEntry.test(metadata)) {
throw new Error("latest-linux.yml does not match the AppImage digest, size, and block map");
}
if ((metadata.match(/^path:/gm) || []).length !== 1 || !new RegExp(`^path: ${escapedName}$`, "m").test(metadata)) {
throw new Error("latest-linux.yml path does not select the expected AppImage");
}
if ((metadata.match(/^sha512:/gm) || []).length !== 1 || !new RegExp(`^sha512: ${escapeRegExp(sha512)}$`, "m").test(metadata)) {
throw new Error("latest-linux.yml top-level digest does not match the AppImage");
}
const checksum = readFileSync(join(directory, "SHA256SUMS-linux.txt"), "utf8");
if (checksum !== `${sha256} ${appImageName}\n`) {
throw new Error("SHA256SUMS-linux.txt does not exactly match the AppImage");
}
return { version, appImageName, size: stats.size, sha512, sha256 };
};
const run = () => {
const [directoryValue, expectedVersion] = process.argv.slice(2);
if (!directoryValue) {
throw new Error("Usage: node scripts/verify-linux-update-release.mjs <directory> [version]");
}
const result = verifyLinuxUpdateRelease({
directory: resolve(directoryValue),
expectedVersion,
});
process.stdout.write(`${basename(directoryValue)}: ${result.appImageName}\n`);
};
if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
run();
}