@rightkit/release 0.2.131 → 0.2.133
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/apple-profile-renew.mjs +220 -0
- package/artifact-storage.mjs +344 -0
- package/build-release.mjs +39 -18
- package/cli/right-release.mjs +9 -3
- package/create-mac-updater.mjs +8 -15
- package/development-signing.mjs +193 -0
- package/development.mjs +15 -13
- package/installer-storage-doctor.mjs +120 -0
- package/macos-notarization.mjs +72 -0
- package/macos-signing-session.mjs +150 -0
- package/mirror-root-artifact.mjs +46 -11
- package/model-promote.mjs +32 -5
- package/notarize-macos.mjs +10 -32
- package/ownership-key-command.mjs +3 -1
- package/ownership-report.mjs +15 -5
- package/package.json +1 -1
- package/publish-update.mjs +14 -6
- package/release.mjs +42 -18
- package/rightkit-signing-agent.mjs +253 -0
- package/rightkit-versions.json +35 -24
- package/runtime-artifact-manifest.mjs +5 -1
- package/sign-macos.mjs +32 -4
- package/sign-updater.mjs +8 -13
- package/signing-agent-client.mjs +85 -0
- package/signing-agent-command.mjs +21 -0
- package/signing-agent-install.mjs +100 -0
- package/signing-receipts.mjs +10 -0
- package/upload-release.mjs +4 -0
package/rightkit-versions.json
CHANGED
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"@rightkit/platform-ui": "0.1.2",
|
|
30
30
|
"@rightkit/qa": "0.3.0",
|
|
31
31
|
"@rightkit/qa-chrome": "0.1.3",
|
|
32
|
-
"@rightkit/release": "0.2.
|
|
32
|
+
"@rightkit/release": "0.2.133",
|
|
33
33
|
"@rightkit/settings-ui": "0.2.7",
|
|
34
34
|
"@rightkit/shell": "0.1.0",
|
|
35
35
|
"@rightkit/tauri": "0.1.1",
|
|
@@ -154,7 +154,9 @@
|
|
|
154
154
|
"0.2.127",
|
|
155
155
|
"0.2.128",
|
|
156
156
|
"0.2.129",
|
|
157
|
-
"0.2.130"
|
|
157
|
+
"0.2.130",
|
|
158
|
+
"0.2.131",
|
|
159
|
+
"0.2.132"
|
|
158
160
|
],
|
|
159
161
|
"@rightkit/qa": [
|
|
160
162
|
"0.1.0",
|
|
@@ -236,7 +238,7 @@
|
|
|
236
238
|
"rightkit-automation": "0.1.0",
|
|
237
239
|
"rightkit-browser": "0.2.2",
|
|
238
240
|
"rightkit-cache": "0.1.0",
|
|
239
|
-
"rightkit-control": "0.1.
|
|
241
|
+
"rightkit-control": "0.1.8",
|
|
240
242
|
"rightkit-crash": "0.1.0",
|
|
241
243
|
"rightkit-db": "0.3.0",
|
|
242
244
|
"rightkit-embed": "0.2.2",
|
|
@@ -246,28 +248,28 @@
|
|
|
246
248
|
"rightkit-jobs": "0.2.2",
|
|
247
249
|
"rightkit-license": "0.1.3",
|
|
248
250
|
"rightkit-logs": "0.2.0",
|
|
249
|
-
"rightkit-mcp": "0.3.
|
|
251
|
+
"rightkit-mcp": "0.3.1",
|
|
250
252
|
"rightkit-media": "0.2.3",
|
|
251
253
|
"rightkit-models": "0.2.2",
|
|
252
254
|
"rightkit-oauth": "0.2.1",
|
|
253
255
|
"rightkit-ort": "0.2.0",
|
|
254
256
|
"rightkit-pdf": "0.1.0",
|
|
255
257
|
"rightkit-playback": "0.1.1",
|
|
256
|
-
"rightkit-process": "0.3.
|
|
257
|
-
"rightkit-qa": "0.2.
|
|
258
|
+
"rightkit-process": "0.3.1",
|
|
259
|
+
"rightkit-qa": "0.2.10",
|
|
258
260
|
"rightkit-qts": "0.2.1",
|
|
259
261
|
"rightkit-qts-ggml": "0.1.0",
|
|
260
262
|
"rightkit-qts-ggml-sys": "0.1.0",
|
|
261
|
-
"rightkit-sandbox": "0.2.
|
|
263
|
+
"rightkit-sandbox": "0.2.4",
|
|
262
264
|
"rightkit-search": "0.1.0",
|
|
263
265
|
"rightkit-secrets": "0.1.0",
|
|
264
266
|
"rightkit-service": "0.1.0",
|
|
265
267
|
"rightkit-settings": "0.1.0",
|
|
266
|
-
"rightkit-shell": "0.1.
|
|
268
|
+
"rightkit-shell": "0.1.3",
|
|
267
269
|
"rightkit-speech-client": "0.3.0",
|
|
268
270
|
"rightkit-sync": "0.1.0",
|
|
269
271
|
"rightkit-tauri": "0.1.1",
|
|
270
|
-
"rightkit-watch": "0.3.
|
|
272
|
+
"rightkit-watch": "0.3.1"
|
|
271
273
|
},
|
|
272
274
|
"swift": {
|
|
273
275
|
"rightkit-swift": {
|
|
@@ -286,7 +288,7 @@
|
|
|
286
288
|
"rightkit-audio": "0.1.1",
|
|
287
289
|
"rightkit-browser": "0.2.2",
|
|
288
290
|
"rightkit-cache": "0.1.0",
|
|
289
|
-
"rightkit-control": "0.1.
|
|
291
|
+
"rightkit-control": "0.1.8",
|
|
290
292
|
"rightkit-crash": "0.1.0",
|
|
291
293
|
"rightkit-db": "0.3.0",
|
|
292
294
|
"rightkit-embed": "0.2.2",
|
|
@@ -296,27 +298,27 @@
|
|
|
296
298
|
"rightkit-jobs": "0.2.2",
|
|
297
299
|
"rightkit-license": "0.1.3",
|
|
298
300
|
"rightkit-logs": "0.2.0",
|
|
299
|
-
"rightkit-mcp": "0.3.
|
|
301
|
+
"rightkit-mcp": "0.3.1",
|
|
300
302
|
"rightkit-media": "0.2.3",
|
|
301
303
|
"rightkit-models": "0.2.2",
|
|
302
304
|
"rightkit-oauth": "0.2.1",
|
|
303
305
|
"rightkit-ort": "0.2.0",
|
|
304
306
|
"rightkit-pdf": "0.1.0",
|
|
305
307
|
"rightkit-playback": "0.1.1",
|
|
306
|
-
"rightkit-process": "0.3.
|
|
307
|
-
"rightkit-qa": "0.2.
|
|
308
|
+
"rightkit-process": "0.3.1",
|
|
309
|
+
"rightkit-qa": "0.2.10",
|
|
308
310
|
"rightkit-qts": "0.2.1",
|
|
309
311
|
"rightkit-qts-ggml": "0.1.0",
|
|
310
312
|
"rightkit-qts-ggml-sys": "0.1.0",
|
|
311
|
-
"rightkit-sandbox": "0.2.
|
|
313
|
+
"rightkit-sandbox": "0.2.4",
|
|
312
314
|
"rightkit-search": "0.1.0",
|
|
313
315
|
"rightkit-secrets": "0.1.0",
|
|
314
316
|
"rightkit-service": "0.1.0",
|
|
315
317
|
"rightkit-settings": "0.1.0",
|
|
316
|
-
"rightkit-shell": "0.1.
|
|
318
|
+
"rightkit-shell": "0.1.3",
|
|
317
319
|
"rightkit-speech-client": "0.3.0",
|
|
318
320
|
"rightkit-tauri": "0.1.1",
|
|
319
|
-
"rightkit-watch": "0.3.
|
|
321
|
+
"rightkit-watch": "0.3.1"
|
|
320
322
|
},
|
|
321
323
|
"npm": {
|
|
322
324
|
"@rightkit/app-shell": "0.2.1",
|
|
@@ -335,7 +337,7 @@
|
|
|
335
337
|
"@rightkit/platform-ui": "0.1.2",
|
|
336
338
|
"@rightkit/qa": "0.3.0",
|
|
337
339
|
"@rightkit/qa-chrome": "0.1.3",
|
|
338
|
-
"@rightkit/release": "0.2.
|
|
340
|
+
"@rightkit/release": "0.2.132",
|
|
339
341
|
"@rightkit/settings-ui": "0.2.7",
|
|
340
342
|
"@rightkit/shell": "0.1.0",
|
|
341
343
|
"@rightkit/tauri": "0.1.1",
|
|
@@ -365,7 +367,8 @@
|
|
|
365
367
|
],
|
|
366
368
|
"rightkit-mcp": [
|
|
367
369
|
"0.1.1",
|
|
368
|
-
"0.2.0"
|
|
370
|
+
"0.2.0",
|
|
371
|
+
"0.3.0"
|
|
369
372
|
],
|
|
370
373
|
"rightkit-media": [
|
|
371
374
|
"0.2.2"
|
|
@@ -380,29 +383,37 @@
|
|
|
380
383
|
"0.1.0"
|
|
381
384
|
],
|
|
382
385
|
"rightkit-process": [
|
|
383
|
-
"0.2.1"
|
|
386
|
+
"0.2.1",
|
|
387
|
+
"0.3.0"
|
|
384
388
|
],
|
|
385
389
|
"rightkit-qa": [
|
|
386
390
|
"0.2.7",
|
|
387
|
-
"0.2.8"
|
|
391
|
+
"0.2.8",
|
|
392
|
+
"0.2.9"
|
|
388
393
|
],
|
|
389
394
|
"rightkit-sandbox": [
|
|
390
395
|
"0.1.0",
|
|
391
|
-
"0.2.0"
|
|
396
|
+
"0.2.0",
|
|
397
|
+
"0.2.1",
|
|
398
|
+
"0.2.2",
|
|
399
|
+
"0.2.3"
|
|
392
400
|
],
|
|
393
401
|
"rightkit-shell": [
|
|
394
402
|
"0.1.0",
|
|
395
|
-
"0.1.1"
|
|
403
|
+
"0.1.1",
|
|
404
|
+
"0.1.2"
|
|
396
405
|
],
|
|
397
406
|
"rightkit-watch": [
|
|
398
407
|
"0.1.0",
|
|
399
|
-
"0.2.0"
|
|
408
|
+
"0.2.0",
|
|
409
|
+
"0.3.0"
|
|
400
410
|
],
|
|
401
411
|
"rightkit-qts": [
|
|
402
412
|
"0.2.0"
|
|
403
413
|
],
|
|
404
414
|
"rightkit-control": [
|
|
405
|
-
"0.1.6"
|
|
415
|
+
"0.1.6",
|
|
416
|
+
"0.1.7"
|
|
406
417
|
]
|
|
407
418
|
}
|
|
408
419
|
}
|
|
@@ -3,6 +3,7 @@ import { sign as edSign, verify as edVerify } from "node:crypto";
|
|
|
3
3
|
export const RUNTIME_ARTIFACT_SCHEMA = 1;
|
|
4
4
|
export const RUNTIME_ARTIFACT_KINDS = Object.freeze([
|
|
5
5
|
"asr-model",
|
|
6
|
+
"tts-model",
|
|
6
7
|
"ocr-model",
|
|
7
8
|
"ort-runtime",
|
|
8
9
|
"media-runtime",
|
|
@@ -15,6 +16,9 @@ export const RIGHT_SUITE_APP_KEYS = Object.freeze([
|
|
|
15
16
|
"mailright",
|
|
16
17
|
"scraperight",
|
|
17
18
|
"coderight",
|
|
19
|
+
"screenright",
|
|
20
|
+
"genright",
|
|
21
|
+
"voiceright",
|
|
18
22
|
]);
|
|
19
23
|
|
|
20
24
|
const TARGET_OSES = new Set(["windows", "darwin", "linux", "ios"]);
|
|
@@ -107,7 +111,7 @@ export function validateRuntimeArtifactManifest(value) {
|
|
|
107
111
|
if (value.entitlement.tier !== lane.tier || value.distribution.bucket !== lane.bucket) {
|
|
108
112
|
fail("distribution lane contradicts entitlement tier or bucket");
|
|
109
113
|
}
|
|
110
|
-
if (["asr-model", "ocr-model", "tokenizer"].includes(value.artifactKind)
|
|
114
|
+
if (["asr-model", "tts-model", "ocr-model", "tokenizer"].includes(value.artifactKind)
|
|
111
115
|
&& (value.distribution.delivery !== "private-r2" || value.entitlement.tier !== "pro")) {
|
|
112
116
|
fail("model artifacts must use private-r2 with Pro entitlement");
|
|
113
117
|
}
|
package/sign-macos.mjs
CHANGED
|
@@ -1,20 +1,26 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
|
-
import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, readlinkSync, statSync, writeFileSync } from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { spawnSync } from "./background-process.mjs";
|
|
6
6
|
import { MACOS_PORTABLE_SIGNING_CONTRACT } from "./macos-signing-contract.mjs";
|
|
7
7
|
import { resolveMacosDeveloperIdIdentity } from "./macos-signing-identity.mjs";
|
|
8
|
+
import { runMacCodesignCommands } from "./macos-signing-session.mjs";
|
|
8
9
|
|
|
9
10
|
const args = process.argv.slice(2);
|
|
10
11
|
const dryRun = args.includes("--dry-run");
|
|
11
12
|
const receiptIndex = args.indexOf("--receipt");
|
|
12
13
|
const receiptPath = receiptIndex >= 0 ? args[receiptIndex + 1] : null;
|
|
14
|
+
const entitlementsIndex = args.indexOf("--entitlements");
|
|
15
|
+
const entitlementsPath = entitlementsIndex >= 0 ? args[entitlementsIndex + 1] : null;
|
|
16
|
+
if (entitlementsIndex >= 0 && (!entitlementsPath || entitlementsPath.startsWith("--"))) fail("--entitlements requires a path");
|
|
13
17
|
const files = args
|
|
14
|
-
.filter((arg, index) => arg !== "--dry-run" && arg !== "--receipt" &&
|
|
18
|
+
.filter((arg, index) => arg !== "--dry-run" && arg !== "--receipt" && arg !== "--entitlements"
|
|
19
|
+
&& (receiptIndex < 0 || index !== receiptIndex + 1)
|
|
20
|
+
&& (entitlementsIndex < 0 || index !== entitlementsIndex + 1))
|
|
15
21
|
.map((arg) => path.resolve(arg));
|
|
16
22
|
|
|
17
|
-
if (!files.length) fail("usage: node sign-macos.mjs [--dry-run] --receipt <path> <file>...");
|
|
23
|
+
if (!files.length) fail("usage: node sign-macos.mjs [--dry-run] [--entitlements <path>] --receipt <path> <file>...");
|
|
18
24
|
if (!receiptPath) fail("--receipt requires a path");
|
|
19
25
|
if (dryRun) {
|
|
20
26
|
console.log(`dry-run: Developer ID signing ${files.join(", ")}`);
|
|
@@ -24,11 +30,16 @@ if (process.platform !== "darwin") fail("macOS signing must run on macOS");
|
|
|
24
30
|
let identity;
|
|
25
31
|
try { identity = resolveMacosDeveloperIdIdentity(); } catch (error) { fail(error.message); }
|
|
26
32
|
for (const file of files) if (!existsSync(file)) fail(`missing file: ${file}`);
|
|
33
|
+
if (entitlementsPath && !existsSync(entitlementsPath)) fail(`missing entitlements: ${entitlementsPath}`);
|
|
27
34
|
|
|
28
35
|
const evidence = [];
|
|
29
36
|
for (const file of files) {
|
|
30
37
|
const before = fileEvidence(file);
|
|
31
|
-
|
|
38
|
+
try {
|
|
39
|
+
const [signed] = runMacCodesignCommands([["--force", "--options", "runtime", "--timestamp", "--sign", identity,
|
|
40
|
+
...(entitlementsPath ? ["--entitlements", path.resolve(entitlementsPath)] : []), file]]);
|
|
41
|
+
if (signed.error || signed.status !== 0) fail(`codesign failed: ${signed.stderr ?? signed.error?.message ?? signed.status}`);
|
|
42
|
+
} catch (error) { fail(`${error.code ? `[${error.code}] ` : ""}${error.message}`); }
|
|
32
43
|
const verification = run("codesign", ["--verify", "--strict", "--verbose=4", file]);
|
|
33
44
|
const details = run("codesign", ["-dvv", file]);
|
|
34
45
|
if (!/valid on disk/i.test(verification) || !/satisfies its designated requirement/i.test(verification)) {
|
|
@@ -46,6 +57,23 @@ mkdirSync(path.dirname(absoluteReceipt), { recursive: true });
|
|
|
46
57
|
writeFileSync(absoluteReceipt, `${JSON.stringify({ schema: 1, signingContract: MACOS_PORTABLE_SIGNING_CONTRACT, identity, files: evidence }, null, 2)}\n`);
|
|
47
58
|
|
|
48
59
|
function fileEvidence(file) {
|
|
60
|
+
if (statSync(file).isDirectory()) {
|
|
61
|
+
const digest = createHash("sha256");
|
|
62
|
+
let sizeBytes = 0;
|
|
63
|
+
function visit(directory) {
|
|
64
|
+
for (const name of readdirSync(directory).sort()) {
|
|
65
|
+
const entry = path.join(directory, name);
|
|
66
|
+
const info = lstatSync(entry);
|
|
67
|
+
const type = info.isSymbolicLink() ? "link" : info.isDirectory() ? "directory" : "file";
|
|
68
|
+
const bytes = type === "link" ? Buffer.from(readlinkSync(entry)) : type === "file" ? readFileSync(entry) : Buffer.alloc(0);
|
|
69
|
+
digest.update(`${path.relative(file, entry).split(path.sep).join("/")}\0${type}\0${bytes.length}\0`).update(bytes);
|
|
70
|
+
sizeBytes += bytes.length;
|
|
71
|
+
if (type === "directory") visit(entry);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
visit(file);
|
|
75
|
+
return { sha256: digest.digest("hex"), sizeBytes, kind: "bundle-tree-v1" };
|
|
76
|
+
}
|
|
49
77
|
const bytes = readFileSync(file);
|
|
50
78
|
return { sha256: createHash("sha256").update(bytes).digest("hex"), sizeBytes: statSync(file).size };
|
|
51
79
|
}
|
package/sign-updater.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { spawnSync } from "./background-process.mjs";
|
|
3
3
|
import { existsSync, readFileSync, rmSync } from "node:fs";
|
|
4
|
-
import os from "node:os";
|
|
5
4
|
import path from "node:path";
|
|
5
|
+
import { signMacUpdaterFiles } from "./macos-signing-session.mjs";
|
|
6
6
|
|
|
7
7
|
const args = process.argv.slice(2);
|
|
8
8
|
const files = [];
|
|
@@ -20,17 +20,7 @@ if (!dryRun && !env.TAURI_SIGNING_PRIVATE_KEY) {
|
|
|
20
20
|
const key = path.join(env.APPDATA || "", "ViewRight", "updater.key");
|
|
21
21
|
if (!existsSync(key)) fail(`shared updater key not found: ${key}`);
|
|
22
22
|
env.TAURI_SIGNING_PRIVATE_KEY = readFileSync(key, "utf8");
|
|
23
|
-
} else if (process.platform
|
|
24
|
-
try {
|
|
25
|
-
env.TAURI_SIGNING_PRIVATE_KEY = execFileSync(
|
|
26
|
-
"security",
|
|
27
|
-
["find-generic-password", "-a", os.userInfo().username, "-s", "rightsuite-updater-key", "-w"],
|
|
28
|
-
{ encoding: "utf8" },
|
|
29
|
-
).trim();
|
|
30
|
-
} catch {
|
|
31
|
-
fail("shared updater key is missing from Keychain service rightsuite-updater-key");
|
|
32
|
-
}
|
|
33
|
-
} else {
|
|
23
|
+
} else if (process.platform !== "darwin") {
|
|
34
24
|
fail("TAURI_SIGNING_PRIVATE_KEY is required on this platform");
|
|
35
25
|
}
|
|
36
26
|
}
|
|
@@ -41,6 +31,7 @@ for (const file of files) {
|
|
|
41
31
|
rmSync(`${file}.sig`, { force: true });
|
|
42
32
|
console.log(`${dryRun ? "[dry-run] " : ""}pnpm exec tauri signer sign ${file}`);
|
|
43
33
|
if (dryRun) continue;
|
|
34
|
+
if (process.platform === "darwin") continue;
|
|
44
35
|
const result = process.platform === "win32"
|
|
45
36
|
? spawnSync(
|
|
46
37
|
process.env.ComSpec || "cmd.exe",
|
|
@@ -58,6 +49,10 @@ for (const file of files) {
|
|
|
58
49
|
});
|
|
59
50
|
if (result.status !== 0) process.exit(result.status ?? 1);
|
|
60
51
|
}
|
|
52
|
+
if (!dryRun && process.platform === "darwin") {
|
|
53
|
+
try { signMacUpdaterFiles(files, { env }); }
|
|
54
|
+
catch (error) { fail(`${error.code ? `[${error.code}] ` : ""}${error.message}`); }
|
|
55
|
+
}
|
|
61
56
|
|
|
62
57
|
function fail(message) {
|
|
63
58
|
console.error(`sign-updater: ${message}`);
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import net from "node:net";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { spawnSync } from "./background-process.mjs";
|
|
7
|
+
import { signingAgentPaths, privatePath, SigningAgentError, MAX_FRAME } from "./rightkit-signing-agent.mjs";
|
|
8
|
+
|
|
9
|
+
export async function requestSigningAgent(request, { paths = signingAgentPaths(), timeoutMs = 185000,
|
|
10
|
+
io = fs, connect = net.createConnection } = {}) {
|
|
11
|
+
try {
|
|
12
|
+
privatePath(paths.directory, 0o700, { io, kind: "directory" });
|
|
13
|
+
privatePath(paths.plist, 0o600, { io });
|
|
14
|
+
privatePath(paths.socket, 0o600, { io, kind: "socket" });
|
|
15
|
+
privatePath(paths.token, 0o600, { io });
|
|
16
|
+
} catch (error) {
|
|
17
|
+
if (error.code === "ENOENT") throw new SigningAgentError("MAC_SIGNING_AGENT_UNAVAILABLE", "Signing agent installation, socket, or token is missing");
|
|
18
|
+
throw error;
|
|
19
|
+
}
|
|
20
|
+
const token = io.readFileSync(paths.token, "utf8");
|
|
21
|
+
if (!/^[a-f0-9]{64}$/.test(token)) throw new SigningAgentError("MAC_SIGNING_AGENT_AUTH", "Invalid signing agent token");
|
|
22
|
+
const frame = Buffer.from(`${JSON.stringify({ ...request, token })}\n`);
|
|
23
|
+
if (frame.length > MAX_FRAME) throw new SigningAgentError("MAC_SIGNING_AGENT_PROTOCOL", "Signing request is too large");
|
|
24
|
+
return new Promise((resolve, reject) => {
|
|
25
|
+
const socket = connect({ path: paths.socket });
|
|
26
|
+
let input = Buffer.alloc(0), settled = false;
|
|
27
|
+
const finish = (error, report) => {
|
|
28
|
+
if (settled) return;
|
|
29
|
+
settled = true; clearTimeout(timer); socket.destroy();
|
|
30
|
+
if (error) reject(error); else resolve(report);
|
|
31
|
+
};
|
|
32
|
+
// Wall deadline, rather than an activity timeout which a stalled peer can extend.
|
|
33
|
+
const timer = setTimeout(() => finish(new SigningAgentError("MAC_SIGNING_AGENT_TIMEOUT", "Signing agent response timed out")), timeoutMs);
|
|
34
|
+
socket.on("connect", () => socket.write(frame));
|
|
35
|
+
socket.on("error", (error) => finish(new SigningAgentError(["ENOENT", "ECONNREFUSED"].includes(error.code)
|
|
36
|
+
? "MAC_SIGNING_AGENT_UNAVAILABLE" : "MAC_SIGNING_AGENT_CONNECTION", "Signing agent connection failed")));
|
|
37
|
+
socket.on("end", () => { if (!settled) finish(new SigningAgentError("MAC_SIGNING_AGENT_PROTOCOL", "Signing agent returned no complete response")); });
|
|
38
|
+
socket.on("data", (chunk) => {
|
|
39
|
+
input = Buffer.concat([input, chunk]);
|
|
40
|
+
if (input.length > MAX_FRAME) { finish(new SigningAgentError("MAC_SIGNING_AGENT_PROTOCOL", "Signing agent response is too large")); return; }
|
|
41
|
+
const end = input.indexOf(10);
|
|
42
|
+
if (end < 0) return;
|
|
43
|
+
try {
|
|
44
|
+
const report = JSON.parse(input.subarray(0, end).toString("utf8"));
|
|
45
|
+
if (end !== input.length - 1 || report.schema !== 1 || typeof report.ok !== "boolean") throw new Error();
|
|
46
|
+
if (!report.ok) {
|
|
47
|
+
const code = typeof report.code === "string" && report.code.startsWith("MAC_SIGNING_") ? report.code : "MAC_SIGNING_AGENT_JOB_FAILED";
|
|
48
|
+
finish(new SigningAgentError(code, request.type === "updater" ? "Updater signing failed inside signing agent" : String(report.message ?? "Signing agent rejected request")));
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
if (request.type === "health") {
|
|
52
|
+
if (report.service !== "dev.rightkit.signing-agent" || !Number.isInteger(report.pid)) throw new Error();
|
|
53
|
+
} else if (!Array.isArray(report.results) || report.results.length !== (request.items?.length ?? request.files?.length)
|
|
54
|
+
|| report.results.some((r) => r.status !== 0 || typeof r.stdout !== "string" || typeof r.stderr !== "string")
|
|
55
|
+
|| (request.type === "updater" && report.results.some((r) => r.stdout || r.stderr))) throw new Error();
|
|
56
|
+
finish(null, report);
|
|
57
|
+
} catch { finish(new SigningAgentError("MAC_SIGNING_AGENT_PROTOCOL", "Invalid signing agent response")); }
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
export function requestSigningAgentSync(request, { runClient = spawnSync, home, timeoutMs = 185000 } = {}) {
|
|
62
|
+
// Existing signing call sites are synchronous. Only typed JSON goes to this
|
|
63
|
+
// child's stdin; credentials & authentication token never enter argv/env.
|
|
64
|
+
const result = runClient(process.execPath, [fileURLToPath(import.meta.url)], { encoding: "utf8", stdio: "pipe", shell: false,
|
|
65
|
+
input: JSON.stringify({ request, home, timeoutMs }), timeout: timeoutMs + 1000, maxBuffer: MAX_FRAME,
|
|
66
|
+
env: { PATH: process.env.PATH, HOME: process.env.HOME } });
|
|
67
|
+
let report;
|
|
68
|
+
try { report = JSON.parse(result.stdout); } catch {
|
|
69
|
+
throw new SigningAgentError(result.error?.code === "ETIMEDOUT" ? "MAC_SIGNING_AGENT_TIMEOUT" : "MAC_SIGNING_AGENT_CONNECTION", "Signing agent client failed");
|
|
70
|
+
}
|
|
71
|
+
if (report.schema !== 1 || typeof report.ok !== "boolean") throw new SigningAgentError("MAC_SIGNING_AGENT_PROTOCOL", "Invalid signing agent client report");
|
|
72
|
+
if (!report.ok) throw new SigningAgentError(report.code ?? "MAC_SIGNING_AGENT_JOB_FAILED", report.message ?? "Signing agent failed");
|
|
73
|
+
if (result.error || result.status !== 0) throw new SigningAgentError("MAC_SIGNING_AGENT_CONNECTION", "Signing agent client failed");
|
|
74
|
+
return report;
|
|
75
|
+
}
|
|
76
|
+
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
77
|
+
try {
|
|
78
|
+
const { request, home, timeoutMs } = JSON.parse(fs.readFileSync(0, "utf8"));
|
|
79
|
+
const report = await requestSigningAgent(request, { paths: signingAgentPaths(home), timeoutMs });
|
|
80
|
+
process.stdout.write(JSON.stringify(report));
|
|
81
|
+
} catch (error) {
|
|
82
|
+
process.stdout.write(JSON.stringify({ schema: 1, ok: false, code: error.code ?? "MAC_SIGNING_AGENT_PROTOCOL", message: error.message }));
|
|
83
|
+
process.exitCode = 1;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { installSigningAgent, uninstallSigningAgent, signingAgentStatus } from "./signing-agent-install.mjs";
|
|
3
|
+
|
|
4
|
+
const [command, ...args] = process.argv.slice(2);
|
|
5
|
+
try {
|
|
6
|
+
let result;
|
|
7
|
+
if (command === "install") {
|
|
8
|
+
const options = {};
|
|
9
|
+
if (args.length) {
|
|
10
|
+
if (args.length !== 2 || args[0] !== "--managed-root" || !args[1]) throw new TypeError("Expected install [--managed-root <absolute-directory>]");
|
|
11
|
+
options.managedRoot = args[1];
|
|
12
|
+
}
|
|
13
|
+
result = installSigningAgent(options);
|
|
14
|
+
} else if (command === "uninstall" && !args.length) result = uninstallSigningAgent();
|
|
15
|
+
else if (command === "status" && !args.length) result = signingAgentStatus();
|
|
16
|
+
else throw new TypeError("Expected signing-agent install|uninstall|status");
|
|
17
|
+
console.log(JSON.stringify(result));
|
|
18
|
+
} catch (error) {
|
|
19
|
+
console.error(`right-release signing-agent: ${error.code ? `[${error.code}] ` : ""}${error.message}`);
|
|
20
|
+
process.exitCode = 1;
|
|
21
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { spawnSync } from "./background-process.mjs";
|
|
5
|
+
import { AGENT_LABEL, signingAgentPaths, defaultSigningRoots, ensurePrivateDirectory, privatePath, SigningAgentError } from "./rightkit-signing-agent.mjs";
|
|
6
|
+
import { requestSigningAgentSync } from "./signing-agent-client.mjs";
|
|
7
|
+
|
|
8
|
+
const capture = { encoding: "utf8", stdio: "pipe", shell: false, timeout: 10000 };
|
|
9
|
+
export function requireMacGuiSession({ run = spawnSync, uid = process.getuid?.() } = {}) {
|
|
10
|
+
const session = run("/bin/launchctl", ["print", `gui/${uid}`], capture);
|
|
11
|
+
if (session.error || session.status !== 0) throw new SigningAgentError("MAC_SIGNING_NO_GUI_SESSION", `No logged-in Aqua session for uid ${uid}`);
|
|
12
|
+
}
|
|
13
|
+
const xml = (value) => String(value).replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
14
|
+
export function signingAgentPlist({ paths, node = process.execPath, home }) {
|
|
15
|
+
return `<?xml version="1.0" encoding="UTF-8"?>\n<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n<plist version="1.0"><dict>
|
|
16
|
+
<key>Label</key><string>${AGENT_LABEL}</string>
|
|
17
|
+
<key>ProgramArguments</key><array><string>${xml(node)}</string><string>${xml(path.join(paths.runtime, "rightkit-signing-agent.mjs"))}</string></array>
|
|
18
|
+
<key>LimitLoadToSessionType</key><string>Aqua</string>
|
|
19
|
+
<key>RunAtLoad</key><true/><key>KeepAlive</key><true/>
|
|
20
|
+
<key>ProcessType</key><string>Background</string>
|
|
21
|
+
<key>ThrottleInterval</key><integer>10</integer>
|
|
22
|
+
<key>Umask</key><integer>63</integer>
|
|
23
|
+
<key>EnvironmentVariables</key><dict><key>HOME</key><string>${xml(home)}</string></dict>
|
|
24
|
+
<key>StandardOutPath</key><string>/dev/null</string>
|
|
25
|
+
<key>StandardErrorPath</key><string>/dev/null</string>
|
|
26
|
+
</dict></plist>\n`;
|
|
27
|
+
}
|
|
28
|
+
export function installSigningAgent({ home = os.userInfo().homedir, username = os.userInfo().username,
|
|
29
|
+
uid = process.getuid?.(), platform = process.platform, run = spawnSync, io = fs, node = process.execPath,
|
|
30
|
+
managedRoot = "/Volumes/D/.rightkit-managed", health = requestSigningAgentSync,
|
|
31
|
+
now = Date.now, sleep = (ms) => Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms) } = {}) {
|
|
32
|
+
if (platform !== "darwin") throw new SigningAgentError("MAC_SIGNING_AGENT_PLATFORM", "Signing agent requires macOS");
|
|
33
|
+
requireMacGuiSession({ run, uid });
|
|
34
|
+
const paths = signingAgentPaths(home);
|
|
35
|
+
ensurePrivateDirectory(paths.directory, { io, uid });
|
|
36
|
+
ensurePrivateDirectory(paths.runtime, { io, uid });
|
|
37
|
+
ensurePrivateDirectory(path.join(paths.directory, "requests"), { io, uid });
|
|
38
|
+
ensurePrivateDirectory(path.join(paths.directory, "tmp"), { io, uid });
|
|
39
|
+
if (!path.isAbsolute(managedRoot) || ["/", home, "/Volumes", "/Volumes/D"].includes(path.resolve(managedRoot))) throw new SigningAgentError("MAC_SIGNING_AGENT_PATH_DENIED", "Managed signing root must be a dedicated absolute directory");
|
|
40
|
+
const found = run("/usr/bin/which", ["pnpm"], capture);
|
|
41
|
+
const pnpm = String(found.stdout ?? "").trim();
|
|
42
|
+
if (found.error || found.status !== 0 || !path.isAbsolute(pnpm) || pnpm.includes("\n")) throw new SigningAgentError("MAC_SIGNING_AGENT_INSTALL_FAILED", "Provisioned pnpm executable is unavailable");
|
|
43
|
+
// Resident runtime is self-contained; no package/checkout imports after install.
|
|
44
|
+
for (const name of ["rightkit-signing-agent.mjs", "background-process.mjs"]) {
|
|
45
|
+
const target = path.join(paths.runtime, name);
|
|
46
|
+
if (io.existsSync(target)) privatePath(target, 0o600, { io, uid });
|
|
47
|
+
io.writeFileSync(target, io.readFileSync(new URL(`./${name}`, import.meta.url)), { mode: 0o600 });
|
|
48
|
+
}
|
|
49
|
+
if (io.existsSync(paths.config)) privatePath(paths.config, 0o600, { io, uid });
|
|
50
|
+
io.writeFileSync(paths.config, JSON.stringify({ schema: 1, home, username, pnpm,
|
|
51
|
+
roots: defaultSigningRoots(home, path.resolve(managedRoot)),
|
|
52
|
+
environmentPath: [...new Set([path.dirname(node), path.dirname(pnpm), "/opt/homebrew/bin", "/usr/local/bin", "/usr/bin", "/bin", "/usr/sbin", "/sbin"])].join(":") }), { mode: 0o600 });
|
|
53
|
+
ensurePrivateDirectory(path.dirname(paths.plist), { io, uid });
|
|
54
|
+
if (io.existsSync(paths.plist)) privatePath(paths.plist, 0o600, { io, uid });
|
|
55
|
+
io.writeFileSync(paths.plist, signingAgentPlist({ paths, node, home }), { mode: 0o600 });
|
|
56
|
+
const loaded = run("/bin/launchctl", ["print", `gui/${uid}/${AGENT_LABEL}`], capture);
|
|
57
|
+
if (!loaded.error && loaded.status === 0) {
|
|
58
|
+
const stopped = run("/bin/launchctl", ["bootout", `gui/${uid}/${AGENT_LABEL}`], capture);
|
|
59
|
+
if (stopped.error || stopped.status !== 0) throw new SigningAgentError("MAC_SIGNING_AGENT_INSTALL_FAILED", "Could not stop existing signing agent");
|
|
60
|
+
}
|
|
61
|
+
const boot = run("/bin/launchctl", ["bootstrap", `gui/${uid}`, paths.plist], capture);
|
|
62
|
+
if (boot.error || boot.status !== 0) {
|
|
63
|
+
requireMacGuiSession({ run, uid });
|
|
64
|
+
throw new SigningAgentError("MAC_SIGNING_AGENT_INSTALL_FAILED", `Signing agent bootstrap failed (exit ${boot.status ?? "unavailable"})`);
|
|
65
|
+
}
|
|
66
|
+
const deadline = now() + 10000;
|
|
67
|
+
do {
|
|
68
|
+
try {
|
|
69
|
+
const report = health({ schema: 1, type: "health" }, { home, timeoutMs: 1000 });
|
|
70
|
+
return { schema: 1, installed: true, running: true, pid: report.pid, socket: paths.socket, plist: paths.plist };
|
|
71
|
+
} catch (error) {
|
|
72
|
+
if (!["MAC_SIGNING_AGENT_UNAVAILABLE", "MAC_SIGNING_AGENT_CONNECTION", "MAC_SIGNING_AGENT_TIMEOUT"].includes(error.code)) throw error;
|
|
73
|
+
sleep(100);
|
|
74
|
+
}
|
|
75
|
+
} while (now() < deadline);
|
|
76
|
+
throw new SigningAgentError("MAC_SIGNING_AGENT_INSTALL_FAILED", "Signing agent bootstrapped but health did not become ready");
|
|
77
|
+
}
|
|
78
|
+
export function signingAgentStatus({ home = os.userInfo().homedir, uid = process.getuid?.(), run = spawnSync,
|
|
79
|
+
io = fs, health = requestSigningAgentSync } = {}) {
|
|
80
|
+
const paths = signingAgentPaths(home);
|
|
81
|
+
const installed = io.existsSync(paths.plist);
|
|
82
|
+
let guiSession = false;
|
|
83
|
+
try { requireMacGuiSession({ run, uid }); guiSession = true; } catch {}
|
|
84
|
+
try {
|
|
85
|
+
const report = health({ schema: 1, type: "health" }, { home, timeoutMs: 1500 });
|
|
86
|
+
return { schema: 1, installed, guiSession, running: true, pid: report.pid, socket: paths.socket };
|
|
87
|
+
} catch (error) { return { schema: 1, installed, guiSession, running: false, code: error.code, socket: paths.socket }; }
|
|
88
|
+
}
|
|
89
|
+
export function uninstallSigningAgent({ home = os.userInfo().homedir, uid = process.getuid?.(), platform = process.platform, run = spawnSync, io = fs } = {}) {
|
|
90
|
+
if (platform !== "darwin") throw new SigningAgentError("MAC_SIGNING_AGENT_PLATFORM", "Signing agent requires macOS");
|
|
91
|
+
const paths = signingAgentPaths(home);
|
|
92
|
+
const loaded = run("/bin/launchctl", ["print", `gui/${uid}/${AGENT_LABEL}`], capture);
|
|
93
|
+
if (!loaded.error && loaded.status === 0) {
|
|
94
|
+
const result = run("/bin/launchctl", ["bootout", `gui/${uid}/${AGENT_LABEL}`], capture);
|
|
95
|
+
if (result.error || result.status !== 0) throw new SigningAgentError("MAC_SIGNING_AGENT_UNINSTALL_FAILED", "Could not stop signing agent");
|
|
96
|
+
}
|
|
97
|
+
if (io.existsSync(paths.plist)) { privatePath(paths.plist, 0o600, { io, uid }); io.unlinkSync(paths.plist); }
|
|
98
|
+
if (io.existsSync(paths.directory)) { privatePath(paths.directory, 0o700, { io, uid, kind: "directory" }); io.rmSync(paths.directory, { recursive: true, force: true }); }
|
|
99
|
+
return { schema: 1, installed: false, running: false };
|
|
100
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
|
|
3
|
+
// One resolver for the worker that writes receipts & the build that seals them.
|
|
4
|
+
export function signingReceiptPath({ appRoot, target, platform, phase, stateRoot }) {
|
|
5
|
+
const configured = platform === "mac"
|
|
6
|
+
? phase === "signing" ? target.sign?.receipt : target.notarize?.receipt
|
|
7
|
+
: phase === "raw-exe" ? target.sign?.receipt : phase === "installer" ? target.sign?.installerReceipt : undefined;
|
|
8
|
+
if (configured) return path.resolve(appRoot, configured);
|
|
9
|
+
return path.resolve(stateRoot ?? path.join(appRoot, ".right-release", "receipts"), `${platform === "mac" ? "macos" : "windows"}-${phase}.json`);
|
|
10
|
+
}
|
package/upload-release.mjs
CHANGED
|
@@ -8,6 +8,7 @@ import { assertPrimaryReleaseCheckout } from "./release-invocation.mjs";
|
|
|
8
8
|
import { verifySealedRelease } from "./release-state.mjs";
|
|
9
9
|
import { assertCleanSource } from "./source-gate.mjs";
|
|
10
10
|
import { verifyOwnershipEvidence } from "./ownership-evidence.mjs";
|
|
11
|
+
import { recordPublishedVersion } from "./artifact-storage.mjs";
|
|
11
12
|
|
|
12
13
|
const args = process.argv.slice(2);
|
|
13
14
|
let platform = process.platform === "win32" ? "win" : process.platform === "darwin" ? "mac" : process.platform;
|
|
@@ -75,6 +76,9 @@ if (!dryRun) writeJson(verifiedMarker, {
|
|
|
75
76
|
tag: result.tag,
|
|
76
77
|
verifiedAt: new Date().toISOString(),
|
|
77
78
|
});
|
|
79
|
+
// This runs only after GitHub has returned successful remote byte verification.
|
|
80
|
+
// Candidate builds never enter published.json.
|
|
81
|
+
if (!dryRun) recordPublishedVersion({ app: sealed.manifest.app, version: sealed.manifest.version, platform });
|
|
78
82
|
console.log(`right-release upload: ${dryRun ? "dry-run " : ""}${result.status} ${releaseId} tier=${tier} repo=${repo}`);
|
|
79
83
|
|
|
80
84
|
function git(cwd, runArgs) {
|