81 lines
3.6 KiB
JavaScript
81 lines
3.6 KiB
JavaScript
|
|
#!/usr/bin/env node
|
||
|
|
// Package and sign an add-on update, ready to publish behind an updateURL.
|
||
|
|
//
|
||
|
|
// node scripts/sign-addon-update.mjs <addon-dir> <base-url> <private-key.pem> [--out <dir>]
|
||
|
|
//
|
||
|
|
// - <addon-dir> path to the local addon folder to publish (must contain addon.json)
|
||
|
|
// - <base-url> canonical URL prefix where the tarball will be hosted;
|
||
|
|
// the tarball is placed at <base-url>/<id>-<version>.tar.gz
|
||
|
|
// - <private-key.pem> PKCS#8 PEM (as written by generate-update-keypair.mjs)
|
||
|
|
// - --out <dir> output dir (default: ./out/); tarball + updates.json entry are written there
|
||
|
|
//
|
||
|
|
// Produces:
|
||
|
|
// <out>/<id>-<version>.tar.gz — the payload the client will fetch
|
||
|
|
// <out>/<id>-<version>.entry.json — one signed entry (append to your live updates.json)
|
||
|
|
//
|
||
|
|
// Publish flow:
|
||
|
|
// 1. scp <out>/<id>-<version>.tar.gz to your web root at <base-url>
|
||
|
|
// 2. update the live updates.json for that add-on to include this entry
|
||
|
|
// (prepend it — the client picks the highest advertised version)
|
||
|
|
// 3. wait for existing installs to fetch on their next boot's +30s tick
|
||
|
|
|
||
|
|
import fs from "node:fs";
|
||
|
|
import path from "node:path";
|
||
|
|
import crypto from "node:crypto";
|
||
|
|
import { execFileSync } from "node:child_process";
|
||
|
|
import process from "node:process";
|
||
|
|
import os from "node:os";
|
||
|
|
|
||
|
|
const SIG_DOMAIN = "silentmode.addon-update-v1";
|
||
|
|
|
||
|
|
function die(msg) { console.error("error: " + msg); process.exit(1); }
|
||
|
|
|
||
|
|
const args = process.argv.slice(2);
|
||
|
|
const outFlagIdx = args.indexOf("--out");
|
||
|
|
const outDir = outFlagIdx >= 0 ? args.splice(outFlagIdx, 2)[1] : path.resolve("out");
|
||
|
|
const [addonDir, baseUrl, keyPath] = args;
|
||
|
|
if (!addonDir || !baseUrl || !keyPath) {
|
||
|
|
console.error("usage: node scripts/sign-addon-update.mjs <addon-dir> <base-url> <private-key.pem> [--out <dir>]");
|
||
|
|
process.exit(2);
|
||
|
|
}
|
||
|
|
|
||
|
|
const addonJsonPath = path.join(addonDir, "addon.json");
|
||
|
|
if (!fs.existsSync(addonJsonPath)) die(`no addon.json in ${addonDir}`);
|
||
|
|
const manifest = JSON.parse(fs.readFileSync(addonJsonPath, "utf8"));
|
||
|
|
const { id, version } = manifest;
|
||
|
|
if (!id || !version) die("addon.json missing id or version");
|
||
|
|
|
||
|
|
const keyPem = fs.readFileSync(keyPath, "utf8");
|
||
|
|
let privateKey;
|
||
|
|
try { privateKey = crypto.createPrivateKey({ key: keyPem, format: "pem" }); }
|
||
|
|
catch (e) { die(`bad private key at ${keyPath}: ${e.message}`); }
|
||
|
|
if (privateKey.asymmetricKeyType !== "ed25519") die("private key must be Ed25519");
|
||
|
|
|
||
|
|
fs.mkdirSync(outDir, { recursive: true });
|
||
|
|
const tarName = `${id}-${version}.tar.gz`;
|
||
|
|
const tarPath = path.join(outDir, tarName);
|
||
|
|
|
||
|
|
// Tar the addon folder from its parent so archived paths are addon-relative.
|
||
|
|
const parent = path.dirname(path.resolve(addonDir));
|
||
|
|
const folder = path.basename(path.resolve(addonDir));
|
||
|
|
try {
|
||
|
|
execFileSync("tar", ["-c", "-z", "-f", tarPath, "-C", parent, folder], { stdio: "inherit" });
|
||
|
|
} catch (e) { die(`tar failed: ${e.message}`); }
|
||
|
|
|
||
|
|
const tarBytes = fs.readFileSync(tarPath);
|
||
|
|
const sha256 = crypto.createHash("sha256").update(tarBytes).digest("hex");
|
||
|
|
const url = baseUrl.replace(/\/+$/, "") + "/" + tarName;
|
||
|
|
|
||
|
|
const canonical = `${SIG_DOMAIN}|${id}|${version}|${sha256}`;
|
||
|
|
const sig = crypto.sign(null, Buffer.from(canonical), privateKey).toString("base64");
|
||
|
|
|
||
|
|
const entry = { version, url, sha256, sig };
|
||
|
|
const entryPath = path.join(outDir, `${id}-${version}.entry.json`);
|
||
|
|
fs.writeFileSync(entryPath, JSON.stringify(entry, null, 2) + "\n");
|
||
|
|
|
||
|
|
console.log(`tarball: ${tarPath} (${tarBytes.length} bytes, ${sha256})`);
|
||
|
|
console.log(`entry: ${entryPath}`);
|
||
|
|
console.log("");
|
||
|
|
console.log("To publish, host the tarball at " + url + " and add this entry to updates.json:");
|
||
|
|
console.log(JSON.stringify(entry, null, 2));
|