@rightkit/release 0.2.134 → 0.2.136
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/README.md +62 -0
- package/addon-command.mjs +53 -34
- package/apple-profile-renew.mjs +30 -12
- package/artifact-storage.mjs +53 -12
- package/background-process.mjs +18 -0
- package/build-release.mjs +4 -3
- package/cache-command.mjs +4 -3
- package/cargo-contract.mjs +1 -1
- package/cargo-guard.mjs +4 -3
- package/cargo-registry-readback.mjs +51 -0
- package/cargo-target.mjs +3 -1
- package/cli/right-release.mjs +26 -0
- package/codesign-argv.mjs +64 -0
- package/codesign-environment.mjs +58 -0
- package/codesign-shim.mjs +51 -0
- package/committed-source.mjs +3 -3
- package/development.mjs +11 -9
- package/direct-bootstrap.mjs +19 -9
- package/dmg-build.mjs +117 -0
- package/dmg-layout.mjs +123 -0
- package/github-release.mjs +74 -37
- package/heavy-command.mjs +3 -2
- package/install-app.mjs +358 -0
- package/inventory-check.mjs +980 -0
- package/macos-notarization.mjs +24 -8
- package/macos-signing-session.mjs +50 -2
- package/managed-cargo.mjs +23 -19
- package/managed-host-secrets.mjs +30 -0
- package/managed-roots.mjs +39 -0
- package/model-promote.mjs +31 -37
- package/notarize-macos.mjs +5 -5
- package/notary-auth.mjs +4 -0
- package/osascript-guard.mjs +24 -0
- package/ownership-approval-command.mjs +15 -4
- package/ownership-approval.mjs +114 -49
- package/ownership-availability.mjs +25 -0
- package/ownership-catalog-config.json +18 -0
- package/ownership-catalog.mjs +94 -0
- package/ownership-command.mjs +17 -10
- package/ownership-declaration.json +15 -0
- package/ownership-default-policy.json +1 -11
- package/ownership-evidence.mjs +10 -8
- package/ownership-git-inventory.mjs +29 -1
- package/ownership-helper-paths.json +4 -4
- package/ownership-hooks.mjs +107 -0
- package/ownership-internal-owners.json +44 -0
- package/ownership-key-authority.mjs +112 -7
- package/ownership-key-command.mjs +76 -19
- package/ownership-key-trust.mjs +97 -9
- package/ownership-lockfiles.mjs +311 -0
- package/ownership-report-command.mjs +12 -3
- package/ownership-report.mjs +20 -17
- package/ownership-repositories.json +19 -0
- package/ownership-scan-worker.mjs +96 -11
- package/ownership-source.mjs +39 -0
- package/ownership-staged.mjs +193 -0
- package/ownership-verdict.mjs +74 -0
- package/ownership.mjs +215 -107
- package/package.json +1 -1
- package/protected-credentials.mjs +111 -0
- package/prune-r2.mjs +4 -3
- package/pub-upload.mjs +3 -85
- package/publication-artifacts.mjs +108 -0
- package/publication-gate.mjs +28 -7
- package/publish-cargo.mjs +47 -16
- package/publish-swift.mjs +46 -17
- package/publish-update.mjs +61 -39
- package/r2-verified-upload.mjs +41 -0
- package/registry-parity.mjs +5 -3
- package/release-state.mjs +1 -0
- package/release-token.mjs +2 -0
- package/release.mjs +2 -1
- package/rightkit-drift.mjs +3 -2
- package/rightkit-ownership.json +2028 -139
- package/rightkit-signing-agent.mjs +161 -19
- package/rightkit-sync.mjs +6 -2
- package/rightkit-versions.json +220 -106
- package/runtime-artifact-manifest.mjs +20 -6
- package/sign-updater.mjs +5 -0
- package/signing-agent-client.mjs +78 -22
- package/signing-agent-command.mjs +5 -4
- package/signing-agent-install.mjs +31 -4
- package/standalone-clone-verify.mjs +3 -2
- package/tauri-bundle-marker.mjs +3 -2
- package/upload-large.mjs +36 -82
- package/verifier.mjs +5 -1
package/signing-agent-client.mjs
CHANGED
|
@@ -6,7 +6,34 @@ import { fileURLToPath } from "node:url";
|
|
|
6
6
|
import { spawnSync } from "./background-process.mjs";
|
|
7
7
|
import { signingAgentPaths, privatePath, SigningAgentError, MAX_FRAME } from "./rightkit-signing-agent.mjs";
|
|
8
8
|
|
|
9
|
-
export
|
|
9
|
+
export function validateNotaryAgentReport(report) {
|
|
10
|
+
const fields = (value, allowed) => value && typeof value === "object" && !Array.isArray(value) && Object.keys(value).every((key) => allowed.includes(key));
|
|
11
|
+
const uuid = (value) => typeof value === "string" && /^[a-f0-9-]{36}$/i.test(value);
|
|
12
|
+
const n = report?.notarization;
|
|
13
|
+
const summary = n?.logSummary;
|
|
14
|
+
if (!fields(report, ["schema", "ok", "id", "notarization", "steps", "stapling", "code", "message"])
|
|
15
|
+
|| report.schema !== 1 || typeof report.ok !== "boolean" || !uuid(report.id)
|
|
16
|
+
|| (report.code !== undefined && !["MAC_SIGNING_AGENT_TIMEOUT", "MAC_SIGNING_AGENT_JOB_FAILED", "MAC_SIGNING_NOTARIZATION_FAILED", "MAC_SIGNING_STAPLE_FAILED"].includes(report.code))
|
|
17
|
+
|| (report.message !== undefined && !["Notarization deadline expired", "Notarization or stapling failed inside signing agent"].includes(report.message))
|
|
18
|
+
|| !fields(n, ["id", "status", "logSummary"]) || !(n.id === null || uuid(n.id))
|
|
19
|
+
|| !["Accepted", "Invalid", "Unknown", "In Progress", "Rejected"].includes(n.status)
|
|
20
|
+
|| !(summary === null || (fields(summary, ["statusCode", "errors", "warnings", "issues"])
|
|
21
|
+
&& (summary.statusCode === null || Number.isInteger(summary.statusCode))
|
|
22
|
+
&& Number.isInteger(summary.errors) && summary.errors >= 0 && Number.isInteger(summary.warnings) && summary.warnings >= 0
|
|
23
|
+
&& Array.isArray(summary.issues) && summary.issues.length <= 100 && summary.issues.every((issue) => fields(issue, ["severity", "code"])
|
|
24
|
+
&& ["error", "warning", "unknown"].includes(issue.severity) && (issue.code === null || Number.isInteger(issue.code)))))
|
|
25
|
+
|| !Array.isArray(report.steps) || report.steps.length > 10 || report.steps.some((step) => !fields(step, ["step", "file", "exitCode"])
|
|
26
|
+
|| !["submit", "log", "probe", "staple", "validate"].includes(step.step)
|
|
27
|
+
|| !(step.exitCode === null || (Number.isInteger(step.exitCode) && step.exitCode >= 0 && step.exitCode <= 255)))
|
|
28
|
+
|| !Array.isArray(report.stapling) || report.stapling.length > 2 || report.stapling.some((item) => !fields(item, ["file", "alreadyStapled", "validated"])
|
|
29
|
+
|| typeof item.file !== "string" || !path.isAbsolute(item.file) || typeof item.alreadyStapled !== "boolean" || item.validated !== true)) {
|
|
30
|
+
throw new SigningAgentError("MAC_SIGNING_AGENT_PROTOCOL", "Invalid notarization agent report");
|
|
31
|
+
}
|
|
32
|
+
return report;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function requestSigningAgent(request, { paths = signingAgentPaths(), timeoutMs = (request.timeoutMs ?? 180000) + 5000,
|
|
36
|
+
onProgress = () => {},
|
|
10
37
|
io = fs, connect = net.createConnection } = {}) {
|
|
11
38
|
try {
|
|
12
39
|
privatePath(paths.directory, 0o700, { io, kind: "directory" });
|
|
@@ -23,7 +50,7 @@ export async function requestSigningAgent(request, { paths = signingAgentPaths()
|
|
|
23
50
|
if (frame.length > MAX_FRAME) throw new SigningAgentError("MAC_SIGNING_AGENT_PROTOCOL", "Signing request is too large");
|
|
24
51
|
return new Promise((resolve, reject) => {
|
|
25
52
|
const socket = connect({ path: paths.socket });
|
|
26
|
-
let input = Buffer.alloc(0), settled = false;
|
|
53
|
+
let input = Buffer.alloc(0), settled = false, jobId;
|
|
27
54
|
const finish = (error, report) => {
|
|
28
55
|
if (settled) return;
|
|
29
56
|
settled = true; clearTimeout(timer); socket.destroy();
|
|
@@ -38,30 +65,53 @@ export async function requestSigningAgent(request, { paths = signingAgentPaths()
|
|
|
38
65
|
socket.on("data", (chunk) => {
|
|
39
66
|
input = Buffer.concat([input, chunk]);
|
|
40
67
|
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
68
|
try {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
69
|
+
while (input.indexOf(10) >= 0 && !settled) {
|
|
70
|
+
const end = input.indexOf(10);
|
|
71
|
+
const report = JSON.parse(input.subarray(0, end).toString("utf8"));
|
|
72
|
+
input = input.subarray(end + 1);
|
|
73
|
+
const notary = ["notarize", "staple"].includes(request.type);
|
|
74
|
+
if (report.event) {
|
|
75
|
+
if (!notary || report.schema !== 1 || !["status", "heartbeat"].includes(report.event)
|
|
76
|
+
|| typeof report.id !== "string" || !/^[a-f0-9-]{36}$/i.test(report.id)
|
|
77
|
+
|| Object.keys(report).some((key) => !["schema", "event", "id", "step"].includes(key))
|
|
78
|
+
|| (report.step !== undefined && !["submit", "log", "probe", "staple", "validate"].includes(report.step))) throw new Error();
|
|
79
|
+
if (jobId && jobId !== report.id) throw new Error();
|
|
80
|
+
jobId = report.id;
|
|
81
|
+
onProgress(report);
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
if (input.length || report.schema !== 1 || typeof report.ok !== "boolean") throw new Error();
|
|
85
|
+
if (notary && report.notarization) validateNotaryAgentReport(report);
|
|
86
|
+
if (notary && jobId && jobId !== report.id) throw new Error();
|
|
87
|
+
if (!report.ok) {
|
|
88
|
+
const code = typeof report.code === "string" && report.code.startsWith("MAC_SIGNING_") ? report.code : "MAC_SIGNING_AGENT_JOB_FAILED";
|
|
89
|
+
const error = new SigningAgentError(code, request.type === "updater" ? "Updater signing failed inside signing agent" : notary ? "Notarization or stapling failed inside signing agent" : String(report.message ?? "Signing agent rejected request"));
|
|
90
|
+
if (notary && report.notarization) error.report = report;
|
|
91
|
+
finish(error);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (notary) {
|
|
95
|
+
validateNotaryAgentReport(report);
|
|
96
|
+
} else if (request.type === "health") {
|
|
97
|
+
if (report.service !== "dev.rightkit.signing-agent" || !Number.isInteger(report.pid)) throw new Error();
|
|
98
|
+
} else if (request.type === "codesign-argv") {
|
|
99
|
+
if (!Array.isArray(report.results) || report.results.length !== 1
|
|
100
|
+
|| report.results.some((r) => !Number.isInteger(r.status) || r.status < 0 || r.status > 255
|
|
101
|
+
|| typeof r.stdout !== "string" || typeof r.stderr !== "string")) throw new Error();
|
|
102
|
+
} else if (!Array.isArray(report.results) || report.results.length !== (request.items?.length ?? request.files?.length)
|
|
103
|
+
|| report.results.some((r) => r.status !== 0 || typeof r.stdout !== "string" || typeof r.stderr !== "string")
|
|
104
|
+
|| (request.type === "updater" && report.results.some((r) => r.stdout || r.stderr))) throw new Error();
|
|
105
|
+
finish(null, report);
|
|
50
106
|
}
|
|
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
107
|
} catch { finish(new SigningAgentError("MAC_SIGNING_AGENT_PROTOCOL", "Invalid signing agent response")); }
|
|
58
108
|
});
|
|
59
109
|
});
|
|
60
110
|
}
|
|
61
|
-
export function requestSigningAgentSync(request, { runClient = spawnSync, home, timeoutMs =
|
|
111
|
+
export function requestSigningAgentSync(request, { runClient = spawnSync, home, timeoutMs = (request.timeoutMs ?? 180000) + 5000 } = {}) {
|
|
62
112
|
// Existing signing call sites are synchronous. Only typed JSON goes to this
|
|
63
113
|
// 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,
|
|
114
|
+
const result = runClient(process.execPath, [fileURLToPath(import.meta.url)], { encoding: "utf8", stdio: ["pipe", "pipe", "inherit"], shell: false,
|
|
65
115
|
input: JSON.stringify({ request, home, timeoutMs }), timeout: timeoutMs + 1000, maxBuffer: MAX_FRAME,
|
|
66
116
|
env: { PATH: process.env.PATH, HOME: process.env.HOME } });
|
|
67
117
|
let report;
|
|
@@ -69,17 +119,23 @@ export function requestSigningAgentSync(request, { runClient = spawnSync, home,
|
|
|
69
119
|
throw new SigningAgentError(result.error?.code === "ETIMEDOUT" ? "MAC_SIGNING_AGENT_TIMEOUT" : "MAC_SIGNING_AGENT_CONNECTION", "Signing agent client failed");
|
|
70
120
|
}
|
|
71
121
|
if (report.schema !== 1 || typeof report.ok !== "boolean") throw new SigningAgentError("MAC_SIGNING_AGENT_PROTOCOL", "Invalid signing agent client report");
|
|
72
|
-
if (!report.ok)
|
|
122
|
+
if (!report.ok) {
|
|
123
|
+
const error = new SigningAgentError(report.code ?? "MAC_SIGNING_AGENT_JOB_FAILED", report.message ?? "Signing agent failed");
|
|
124
|
+
if (["notarize", "staple"].includes(request.type) && report.notarization) error.report = validateNotaryAgentReport(report);
|
|
125
|
+
throw error;
|
|
126
|
+
}
|
|
73
127
|
if (result.error || result.status !== 0) throw new SigningAgentError("MAC_SIGNING_AGENT_CONNECTION", "Signing agent client failed");
|
|
74
128
|
return report;
|
|
75
129
|
}
|
|
76
|
-
|
|
130
|
+
const realOrSelf = (file) => { try { return fs.realpathSync(file); } catch { return path.resolve(file); } };
|
|
131
|
+
if (process.argv[1] && realOrSelf(process.argv[1]) === realOrSelf(fileURLToPath(import.meta.url))) {
|
|
77
132
|
try {
|
|
78
133
|
const { request, home, timeoutMs } = JSON.parse(fs.readFileSync(0, "utf8"));
|
|
79
|
-
const report = await requestSigningAgent(request, { paths: signingAgentPaths(home), timeoutMs
|
|
134
|
+
const report = await requestSigningAgent(request, { paths: signingAgentPaths(home), timeoutMs,
|
|
135
|
+
onProgress: (frame) => process.stderr.write(`right-release signing-agent: ${frame.id} ${frame.step ?? frame.event}\n`) });
|
|
80
136
|
process.stdout.write(JSON.stringify(report));
|
|
81
137
|
} catch (error) {
|
|
82
|
-
process.stdout.write(JSON.stringify({ schema: 1, ok: false, code: error.code ?? "MAC_SIGNING_AGENT_PROTOCOL", message: error.message }));
|
|
138
|
+
process.stdout.write(JSON.stringify(error.report ?? { schema: 1, ok: false, code: error.code ?? "MAC_SIGNING_AGENT_PROTOCOL", message: error.message }));
|
|
83
139
|
process.exitCode = 1;
|
|
84
140
|
}
|
|
85
141
|
}
|
|
@@ -5,10 +5,11 @@ const [command, ...args] = process.argv.slice(2);
|
|
|
5
5
|
try {
|
|
6
6
|
let result;
|
|
7
7
|
if (command === "install") {
|
|
8
|
-
const options = {};
|
|
9
|
-
|
|
10
|
-
if (
|
|
11
|
-
options.managedRoot = args[1];
|
|
8
|
+
const options = { cargoTargetDirs: [] };
|
|
9
|
+
for (let i = 0; i < args.length; i += 2) {
|
|
10
|
+
if (!["--managed-root", "--cargo-target-dir"].includes(args[i]) || !args[i + 1]) throw new TypeError("Expected install [--managed-root <directory>] [--cargo-target-dir <directory>]...");
|
|
11
|
+
if (args[i] === "--managed-root") options.managedRoot = args[i + 1];
|
|
12
|
+
else options.cargoTargetDirs.push(args[i + 1]);
|
|
12
13
|
}
|
|
13
14
|
result = installSigningAgent(options);
|
|
14
15
|
} else if (command === "uninstall" && !args.length) result = uninstallSigningAgent();
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { resolveManagedRoots } from './managed-roots.mjs';
|
|
1
2
|
import * as fs from "node:fs";
|
|
2
3
|
import os from "node:os";
|
|
3
4
|
import path from "node:path";
|
|
@@ -6,6 +7,8 @@ import { AGENT_LABEL, signingAgentPaths, defaultSigningRoots, ensurePrivateDirec
|
|
|
6
7
|
import { requestSigningAgentSync } from "./signing-agent-client.mjs";
|
|
7
8
|
|
|
8
9
|
const capture = { encoding: "utf8", stdio: "pipe", shell: false, timeout: 10000 };
|
|
10
|
+
function sleepMs(ms) { Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms); }
|
|
11
|
+
|
|
9
12
|
export function requireMacGuiSession({ run = spawnSync, uid = process.getuid?.() } = {}) {
|
|
10
13
|
const session = run("/bin/launchctl", ["print", `gui/${uid}`], capture);
|
|
11
14
|
if (session.error || session.status !== 0) throw new SigningAgentError("MAC_SIGNING_NO_GUI_SESSION", `No logged-in Aqua session for uid ${uid}`);
|
|
@@ -27,7 +30,7 @@ export function signingAgentPlist({ paths, node = process.execPath, home }) {
|
|
|
27
30
|
}
|
|
28
31
|
export function installSigningAgent({ home = os.userInfo().homedir, username = os.userInfo().username,
|
|
29
32
|
uid = process.getuid?.(), platform = process.platform, run = spawnSync, io = fs, node = process.execPath,
|
|
30
|
-
managedRoot =
|
|
33
|
+
managedRoot = resolveManagedRoots({ home }).dataRoot, cargoTargetDirs = [], health = requestSigningAgentSync,
|
|
31
34
|
now = Date.now, sleep = (ms) => Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms) } = {}) {
|
|
32
35
|
if (platform !== "darwin") throw new SigningAgentError("MAC_SIGNING_AGENT_PLATFORM", "Signing agent requires macOS");
|
|
33
36
|
requireMacGuiSession({ run, uid });
|
|
@@ -41,14 +44,26 @@ export function installSigningAgent({ home = os.userInfo().homedir, username = o
|
|
|
41
44
|
const pnpm = String(found.stdout ?? "").trim();
|
|
42
45
|
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
46
|
// Resident runtime is self-contained; no package/checkout imports after install.
|
|
44
|
-
for (const
|
|
47
|
+
for (const root of cargoTargetDirs) {
|
|
48
|
+
if (typeof root !== "string" || !path.isAbsolute(root) || ["/", home, "/Volumes", "/Volumes/D"].includes(path.resolve(root))) throw new SigningAgentError("MAC_SIGNING_AGENT_PATH_DENIED", "Cargo target root must be a dedicated absolute directory");
|
|
49
|
+
}
|
|
50
|
+
for (const name of ["rightkit-signing-agent.mjs", "background-process.mjs", "codesign-argv.mjs", "managed-roots.mjs"]) {
|
|
45
51
|
const target = path.join(paths.runtime, name);
|
|
46
52
|
if (io.existsSync(target)) privatePath(target, 0o600, { io, uid });
|
|
47
53
|
io.writeFileSync(target, io.readFileSync(new URL(`./${name}`, import.meta.url)), { mode: 0o600 });
|
|
48
54
|
}
|
|
49
55
|
if (io.existsSync(paths.config)) privatePath(paths.config, 0o600, { io, uid });
|
|
56
|
+
// SSH & Aqua can inherit different TMPDIR spellings. Include both aliases of
|
|
57
|
+
// this uid's Darwin temp root, since bundlers can canonicalize temporary paths.
|
|
58
|
+
const temp = run("/usr/bin/getconf", ["DARWIN_USER_TEMP_DIR"], capture);
|
|
59
|
+
const directory = String(temp.stdout ?? "").trim();
|
|
60
|
+
const temporaryRoots = [];
|
|
61
|
+
if (!temp.error && temp.status === 0 && path.isAbsolute(directory) && !directory.includes("\n")) {
|
|
62
|
+
temporaryRoots.push(path.resolve(directory));
|
|
63
|
+
try { temporaryRoots.push((io.realpathSync.native ?? io.realpathSync)(directory)); } catch {}
|
|
64
|
+
}
|
|
50
65
|
io.writeFileSync(paths.config, JSON.stringify({ schema: 1, home, username, pnpm,
|
|
51
|
-
roots: defaultSigningRoots(home, path.resolve(managedRoot)),
|
|
66
|
+
roots: defaultSigningRoots(home, path.resolve(managedRoot), [...cargoTargetDirs.map((root) => path.resolve(root)), ...temporaryRoots]),
|
|
52
67
|
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
68
|
ensurePrivateDirectory(path.dirname(paths.plist), { io, uid });
|
|
54
69
|
if (io.existsSync(paths.plist)) privatePath(paths.plist, 0o600, { io, uid });
|
|
@@ -57,8 +72,20 @@ export function installSigningAgent({ home = os.userInfo().homedir, username = o
|
|
|
57
72
|
if (!loaded.error && loaded.status === 0) {
|
|
58
73
|
const stopped = run("/bin/launchctl", ["bootout", `gui/${uid}/${AGENT_LABEL}`], capture);
|
|
59
74
|
if (stopped.error || stopped.status !== 0) throw new SigningAgentError("MAC_SIGNING_AGENT_INSTALL_FAILED", "Could not stop existing signing agent");
|
|
75
|
+
// bootout is asynchronous: wait until launchd has released the label before bootstrapping again.
|
|
76
|
+
const gone = now() + 10000;
|
|
77
|
+
while (now() < gone) {
|
|
78
|
+
const still = run("/bin/launchctl", ["print", `gui/${uid}/${AGENT_LABEL}`], capture);
|
|
79
|
+
if (still.error || still.status !== 0) break;
|
|
80
|
+
sleepMs(100);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
let boot = run("/bin/launchctl", ["bootstrap", `gui/${uid}`, paths.plist], capture);
|
|
84
|
+
// Exit 5 (EIO) is launchd still tearing the old job down; retry briefly.
|
|
85
|
+
for (let attempt = 0; attempt < 20 && !boot.error && boot.status === 5; attempt++) {
|
|
86
|
+
sleepMs(250);
|
|
87
|
+
boot = run("/bin/launchctl", ["bootstrap", `gui/${uid}`, paths.plist], capture);
|
|
60
88
|
}
|
|
61
|
-
const boot = run("/bin/launchctl", ["bootstrap", `gui/${uid}`, paths.plist], capture);
|
|
62
89
|
if (boot.error || boot.status !== 0) {
|
|
63
90
|
requireMacGuiSession({ run, uid });
|
|
64
91
|
throw new SigningAgentError("MAC_SIGNING_AGENT_INSTALL_FAILED", `Signing agent bootstrap failed (exit ${boot.status ?? "unavailable"})`);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync, mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
1
|
+
import { existsSync, mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync, realpathSync } from "node:fs";
|
|
2
2
|
import { tmpdir } from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { spawnSync } from "./background-process.mjs";
|
|
@@ -140,7 +140,8 @@ function parseArgs(argv) {
|
|
|
140
140
|
return { apps, evidencePath };
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
-
|
|
143
|
+
const realOrSelf = (file) => { try { return realpathSync(file); } catch { return path.resolve(file); } };
|
|
144
|
+
if (process.argv[1] && realOrSelf(process.argv[1]) === realOrSelf(fileURLToPath(import.meta.url))) {
|
|
144
145
|
try {
|
|
145
146
|
const evidence = await verifyStandaloneClones(parseArgs(process.argv.slice(2)));
|
|
146
147
|
for (const app of evidence.apps) process.stdout.write(`[standalone] ${app.key} ${app.revision.slice(0, 12)} install+doctor passed\n`);
|
package/tauri-bundle-marker.mjs
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
// a byte changing outside the marker all mean the assumption no longer holds.
|
|
11
11
|
|
|
12
12
|
import { createHash } from "node:crypto";
|
|
13
|
-
import { readFileSync, writeFileSync } from "node:fs";
|
|
13
|
+
import { readFileSync, writeFileSync, realpathSync } from "node:fs";
|
|
14
14
|
import path from "node:path";
|
|
15
15
|
import { fileURLToPath } from "node:url";
|
|
16
16
|
|
|
@@ -147,7 +147,8 @@ function sha256(bytes) {
|
|
|
147
147
|
return createHash("sha256").update(bytes).digest("hex");
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
-
|
|
150
|
+
const realOrSelf = (file) => { try { return realpathSync(file); } catch { return path.resolve(file); } };
|
|
151
|
+
if (process.argv[1] && realOrSelf(process.argv[1]) === realOrSelf(fileURLToPath(import.meta.url))) {
|
|
151
152
|
const argv = process.argv.slice(2);
|
|
152
153
|
const bundleIndex = argv.indexOf("--bundle");
|
|
153
154
|
const bundle = bundleIndex >= 0 ? argv[bundleIndex + 1] : "nsis";
|
package/upload-large.mjs
CHANGED
|
@@ -1,86 +1,40 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
4
5
|
import { spawnSync } from "./background-process.mjs";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
]
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
process.
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
console.
|
|
39
|
-
|
|
40
|
-
// Resolve the package runner before the dry-run exit: a release box without one cannot upload,
|
|
41
|
-
// and that must fail here rather than after a signed build has already been produced.
|
|
42
|
-
const runner = resolveRunner(env);
|
|
43
|
-
if (!runner) {
|
|
44
|
-
console.error(
|
|
45
|
-
"upload-large: no package runner found. Tried: " +
|
|
46
|
-
"pnpm dlx. Install the pinned workspace package manager before release.",
|
|
47
|
-
);
|
|
48
|
-
process.exit(1);
|
|
49
|
-
}
|
|
50
|
-
console.log(`runner: ${[runner.cmd, ...runner.prefix].join(" ")}`);
|
|
51
|
-
|
|
52
|
-
if (process.env.RIGHT_RELEASE_UPLOAD_DRY_RUN === "1") {
|
|
53
|
-
process.exit(0);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const result = spawnSync(runner.cmd, [...runner.prefix, ...wranglerArgs], {
|
|
57
|
-
env,
|
|
58
|
-
stdio: "inherit",
|
|
59
|
-
shell: process.platform === "win32",
|
|
60
|
-
windowsHide: true,
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
if (result.error) {
|
|
64
|
-
console.error(`upload-large: failed to start wrangler: ${result.error.message}`);
|
|
65
|
-
process.exit(1);
|
|
66
|
-
}
|
|
67
|
-
process.exit(result.status ?? 1);
|
|
68
|
-
|
|
69
|
-
function resolveRunner(env) {
|
|
70
|
-
const probe = spawnSync(RUNNER.cmd, ["--version"], {
|
|
71
|
-
env,
|
|
72
|
-
stdio: "ignore",
|
|
73
|
-
shell: process.platform === "win32",
|
|
74
|
-
windowsHide: true,
|
|
75
|
-
});
|
|
76
|
-
return !probe.error && probe.status === 0 ? RUNNER : null;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function cleanCloudflareEnv(source) {
|
|
80
|
-
const env = { ...source, CLOUDFLARE_ACCOUNT_ID: source.CLOUDFLARE_ACCOUNT_ID || ACCOUNT_ID };
|
|
81
|
-
delete env.CLOUDFLARE_API_KEY;
|
|
82
|
-
delete env.CLOUDFLARE_EMAIL;
|
|
83
|
-
delete env.CF_API_KEY;
|
|
84
|
-
delete env.CF_EMAIL;
|
|
85
|
-
return env;
|
|
6
|
+
import { verifySealedRelease } from "./release-state.mjs";
|
|
7
|
+
import { uploadVerifiedR2File } from "./r2-verified-upload.mjs";
|
|
8
|
+
import { resolveCloudflareApiToken } from "./protected-credentials.mjs";
|
|
9
|
+
|
|
10
|
+
export function runLargeUpload(argv, { run = spawnSync, env = process.env, credentialStore } = {}) {
|
|
11
|
+
const [localFile, r2Key, bucketAlias = "public", flag, evidencePath, ...extra] = argv;
|
|
12
|
+
if (!localFile || !r2Key) throw new Error("Usage: upload-large <file> <key> public|private --evidence <json> | --seal <sealed-dir>");
|
|
13
|
+
if (!["public", "private"].includes(bucketAlias)) throw new Error(`invalid bucket alias ${bucketAlias}; expected public|private`);
|
|
14
|
+
if (!["--evidence", "--seal"].includes(flag) || !evidencePath || extra.length) throw new Error("publication seal or hash evidence is required (--seal or --evidence)");
|
|
15
|
+
let evidence;
|
|
16
|
+
if (flag === "--seal") {
|
|
17
|
+
const sealed = verifySealedRelease(path.resolve(evidencePath));
|
|
18
|
+
if (path.dirname(path.resolve(localFile)) !== path.resolve(sealed.sealedDir)) throw new Error("artifact is outside sealed directory");
|
|
19
|
+
evidence = sealed.manifest.files.find((entry) => entry.name === path.basename(localFile));
|
|
20
|
+
} else {
|
|
21
|
+
const value = JSON.parse(readFileSync(evidencePath, "utf8"));
|
|
22
|
+
evidence = { sha256: value.artifactSha256, sizeBytes: value.sizeBytes };
|
|
23
|
+
}
|
|
24
|
+
// Validate before runner discovery or any remote operation, including dry runs.
|
|
25
|
+
uploadVerifiedR2File(localFile, evidence, r2Key, bucketAlias, { dryRun: true });
|
|
26
|
+
// The Cloudflare token is resolved only for a real upload; it is injected into the Wrangler child environment alone.
|
|
27
|
+
const dryRun = env.RIGHT_RELEASE_UPLOAD_DRY_RUN === "1";
|
|
28
|
+
const token = resolveCloudflareApiToken({ env, credentialStore, required: !dryRun });
|
|
29
|
+
const uploadEnv = dryRun ? env : { ...env, CLOUDFLARE_API_TOKEN: token };
|
|
30
|
+
const probe = run("pnpm", ["--version"], { env, stdio: "ignore", shell: process.platform === "win32", windowsHide: true });
|
|
31
|
+
if (probe.error || probe.status !== 0) throw new Error("no package runner found: pnpm dlx");
|
|
32
|
+
const object = `${bucketAlias === "public" ? "rightapps-downloads" : "rightapps-updates"}/${r2Key}`;
|
|
33
|
+
console.log(`wrangler r2 object put ${object} --remote (mandatory hash readback)`);
|
|
34
|
+
console.log("runner: pnpm dlx");
|
|
35
|
+
return uploadVerifiedR2File(localFile, evidence, r2Key, bucketAlias, { run, env: uploadEnv, dryRun });
|
|
36
|
+
}
|
|
37
|
+
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
38
|
+
try { runLargeUpload(process.argv.slice(2)); }
|
|
39
|
+
catch (error) { console.error(`upload-large: ${error.message}`); process.exitCode = 1; }
|
|
86
40
|
}
|
package/verifier.mjs
CHANGED
|
@@ -13,6 +13,9 @@
|
|
|
13
13
|
* human nor a model can hand-edit its way to a grant.
|
|
14
14
|
*/
|
|
15
15
|
import { createHash } from "node:crypto";
|
|
16
|
+
import { realpathSync } from "node:fs";
|
|
17
|
+
import path from "node:path";
|
|
18
|
+
import { fileURLToPath } from "node:url";
|
|
16
19
|
|
|
17
20
|
export const GRANTS = Object.freeze(["BUILD_AUTHORIZED", "SIGNING_AUTHORIZED", "RELEASE_AUTHORIZED"]);
|
|
18
21
|
|
|
@@ -136,7 +139,8 @@ function decision(grant, granted, issues, input) {
|
|
|
136
139
|
return body;
|
|
137
140
|
}
|
|
138
141
|
|
|
139
|
-
const
|
|
142
|
+
const realOrSelf = (file) => { try { return realpathSync(file); } catch { return path.resolve(file); } };
|
|
143
|
+
const invokedDirectly = process.argv[1] && realOrSelf(process.argv[1]) === realOrSelf(fileURLToPath(import.meta.url));
|
|
140
144
|
if (invokedDirectly) {
|
|
141
145
|
const grant = process.argv[2];
|
|
142
146
|
let input = {};
|