f34266b704
Hop-State: A_06FNNTFDVDZGB2NFDAARAT0 Hop-Proposal: R_06FNNTEE6NDAD98WA9S7VA0 Hop-Task: T_06FNNS0FK2T1TH9QVHG8MSR Hop-Attempt: AT_06FNNS0FK2MH777VNTJ1530
143 lines
6.1 KiB
JavaScript
143 lines
6.1 KiB
JavaScript
import { createHash } from "node:crypto";
|
|
import { execFileSync } from "node:child_process";
|
|
import { chmod, copyFile, mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
import { tmpdir } from "node:os";
|
|
import { dirname, join, resolve } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const VERSION = "0.61.2";
|
|
const RELEASE_API = `https://api.github.com/repos/navidrome/navidrome/releases/tags/v${VERSION}`;
|
|
const FFMPEG_RELEASE = "b6.1.1";
|
|
const FFMPEG_RELEASE_API = `https://api.github.com/repos/eugeneware/ffmpeg-static/releases/tags/${FFMPEG_RELEASE}`;
|
|
const targetTriple = execFileSync("rustc", ["--print", "host-tuple"], { encoding: "utf8" }).trim();
|
|
const projectRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
|
|
const archives = {
|
|
"aarch64-apple-darwin": `navidrome_${VERSION}_darwin_arm64.tar.gz`,
|
|
"x86_64-apple-darwin": `navidrome_${VERSION}_darwin_amd64.tar.gz`,
|
|
"aarch64-unknown-linux-gnu": `navidrome_${VERSION}_linux_arm64.tar.gz`,
|
|
"x86_64-unknown-linux-gnu": `navidrome_${VERSION}_linux_amd64.tar.gz`,
|
|
"x86_64-pc-windows-msvc": `navidrome_${VERSION}_windows_amd64.zip`,
|
|
};
|
|
|
|
const ffmpegPlatforms = {
|
|
"aarch64-apple-darwin": "darwin-arm64",
|
|
"x86_64-apple-darwin": "darwin-x64",
|
|
"aarch64-unknown-linux-gnu": "linux-arm64",
|
|
"x86_64-unknown-linux-gnu": "linux-x64",
|
|
"x86_64-pc-windows-msvc": "win32-x64",
|
|
};
|
|
|
|
const archiveName = archives[targetTriple];
|
|
const ffmpegPlatform = ffmpegPlatforms[targetTriple];
|
|
if (!archiveName || !ffmpegPlatform) {
|
|
throw new Error(`Resonant's media sidecars are not configured for Rust target ${targetTriple}.`);
|
|
}
|
|
|
|
const extension = targetTriple.includes("windows") ? ".exe" : "";
|
|
const outputDirectory = join(projectRoot, "src-tauri/binaries");
|
|
const outputPath = join(outputDirectory, `navidrome-${targetTriple}${extension}`);
|
|
const ffmpegOutputPath = join(outputDirectory, `ffmpeg-${targetTriple}${extension}`);
|
|
const resourceDirectory = join(projectRoot, "src-tauri/resources");
|
|
const licensePath = join(resourceDirectory, "navidrome-LICENSE");
|
|
const ffmpegLicensePath = join(resourceDirectory, "ffmpeg-LICENSE");
|
|
const ffmpegReadmePath = join(resourceDirectory, "ffmpeg-README");
|
|
|
|
await mkdir(outputDirectory, { recursive: true });
|
|
await mkdir(resourceDirectory, { recursive: true });
|
|
let binaryReady = false;
|
|
let licenseReady = false;
|
|
let ffmpegReady = false;
|
|
let ffmpegLicenseReady = false;
|
|
let ffmpegReadmeReady = false;
|
|
try {
|
|
await readFile(outputPath);
|
|
binaryReady = true;
|
|
} catch {
|
|
// Download the pinned official release below when missing.
|
|
}
|
|
try {
|
|
await readFile(licensePath);
|
|
licenseReady = true;
|
|
} catch {
|
|
// Include Navidrome's GPL license in the packaged app when missing.
|
|
}
|
|
try {
|
|
await readFile(ffmpegOutputPath);
|
|
ffmpegReady = true;
|
|
} catch {
|
|
// Download the pinned release asset below when missing.
|
|
}
|
|
try {
|
|
await readFile(ffmpegLicensePath);
|
|
ffmpegLicenseReady = true;
|
|
} catch {
|
|
// Download the matching binary license below when missing.
|
|
}
|
|
try {
|
|
await readFile(ffmpegReadmePath);
|
|
ffmpegReadmeReady = true;
|
|
} catch {
|
|
// Download the matching binary build information below when missing.
|
|
}
|
|
|
|
const headers = { Accept: "application/vnd.github+json", "User-Agent": "Resonant-sidecar-preparer" };
|
|
const verifiedReleaseAsset = async (release, assetName) => {
|
|
const asset = release.assets.find((candidate) => candidate.name === assetName);
|
|
if (!asset) throw new Error(`The pinned release does not contain ${assetName}.`);
|
|
const response = await fetch(asset.browser_download_url, { headers });
|
|
if (!response.ok) throw new Error(`Could not download ${assetName} (${response.status}).`);
|
|
const bytes = Buffer.from(await response.arrayBuffer());
|
|
const actualDigest = createHash("sha256").update(bytes).digest("hex");
|
|
const expectedDigest = asset.digest?.replace(/^sha256:/, "");
|
|
if (!expectedDigest || actualDigest !== expectedDigest) {
|
|
throw new Error(`Checksum verification failed for ${assetName}.`);
|
|
}
|
|
return bytes;
|
|
};
|
|
|
|
if (!ffmpegReady || !ffmpegLicenseReady || !ffmpegReadmeReady) {
|
|
const releaseResponse = await fetch(FFMPEG_RELEASE_API, { headers });
|
|
if (!releaseResponse.ok) throw new Error(`Could not read FFmpeg ${FFMPEG_RELEASE} release metadata (${releaseResponse.status}).`);
|
|
const release = await releaseResponse.json();
|
|
if (!ffmpegReady) {
|
|
await writeFile(ffmpegOutputPath, await verifiedReleaseAsset(release, `ffmpeg-${ffmpegPlatform}`));
|
|
if (!extension) await chmod(ffmpegOutputPath, 0o755);
|
|
}
|
|
if (!ffmpegLicenseReady) {
|
|
await writeFile(ffmpegLicensePath, await verifiedReleaseAsset(release, `${ffmpegPlatform}.LICENSE`));
|
|
}
|
|
if (!ffmpegReadmeReady) {
|
|
await writeFile(ffmpegReadmePath, await verifiedReleaseAsset(release, `${ffmpegPlatform}.README`));
|
|
}
|
|
}
|
|
|
|
if (!licenseReady) {
|
|
const licenseResponse = await fetch(`https://raw.githubusercontent.com/navidrome/navidrome/v${VERSION}/LICENSE`, { headers });
|
|
if (!licenseResponse.ok) throw new Error(`Could not download the Navidrome license (${licenseResponse.status}).`);
|
|
await writeFile(licensePath, await licenseResponse.text());
|
|
}
|
|
if (binaryReady) {
|
|
process.stdout.write(`Navidrome ${VERSION} and FFmpeg sidecars are ready for ${targetTriple}.\n`);
|
|
process.exit(0);
|
|
}
|
|
|
|
const releaseResponse = await fetch(RELEASE_API, { headers });
|
|
if (!releaseResponse.ok) throw new Error(`Could not read the Navidrome release metadata (${releaseResponse.status}).`);
|
|
const release = await releaseResponse.json();
|
|
const archive = await verifiedReleaseAsset(release, archiveName);
|
|
|
|
const workingDirectory = await mkdtemp(join(tmpdir(), "resonant-navidrome-"));
|
|
try {
|
|
const archivePath = join(workingDirectory, archiveName);
|
|
await writeFile(archivePath, archive);
|
|
execFileSync("tar", ["-xf", archivePath], { cwd: workingDirectory, stdio: "inherit" });
|
|
const extractedBinary = join(workingDirectory, `navidrome${extension}`);
|
|
await copyFile(extractedBinary, outputPath);
|
|
if (!extension) await chmod(outputPath, 0o755);
|
|
} finally {
|
|
await rm(workingDirectory, { recursive: true, force: true });
|
|
}
|
|
|
|
process.stdout.write(`Downloaded and verified Navidrome ${VERSION}; FFmpeg ${FFMPEG_RELEASE} is ready for ${targetTriple}.\n`);
|