@catalyst-cloud/cli 0.16.3 → 0.16.6
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/CHANGELOG.md +8 -0
- package/dist/onboard-next.js +1 -0
- package/dist/onboard-runner-custody.js +680 -0
- package/dist/onboard-runner.js +181 -45
- package/package.json +4 -4
- package/skills/catalyst-github/SKILL.md +1 -1
- package/skills/catalyst-linear/SKILL.md +1 -1
- package/skills/catalyst-onboard/SKILL.md +2 -1
- package/skills/catalyst-onboard/references/preserving-runner-replacement.md +26 -0
- package/skills/run-this-project/SKILL.md +1 -1
- package/skills/unstick/SKILL.md +1 -1
- package/skills/what-needs-me/SKILL.md +1 -1
- package/skills/what-this-repo-needs/SKILL.md +1 -1
- package/skills/whats-happening/SKILL.md +1 -1
- package/vendor/self-host/compose.darwin-thoughts.yaml +18 -0
- package/vendor/self-host/compose.yaml +21 -3
- package/vendor/self-host/darwin-thoughts-custody/manifest.json +18 -0
- package/vendor/self-host/darwin-thoughts-custody/producer.mjs +1202 -0
- package/vendor/self-host/darwin-thoughts-custody/service.plist.in +21 -0
- package/vendor/self-host/darwin-thoughts-custody/verifier.d.mts +157 -0
- package/vendor/self-host/darwin-thoughts-custody/verifier.mjs +755 -0
- package/vendor/self-host/darwin-thoughts-custody/watchdog-deadline.mjs +39 -0
- package/vendor/self-host/darwin-thoughts-custody/watchdog.mjs +85 -0
- package/vendor/self-host/provenance.json +10 -2
|
@@ -0,0 +1,1202 @@
|
|
|
1
|
+
// packages/host-isolation/src/darwin-thoughts-producer.ts
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import {
|
|
4
|
+
createHash,
|
|
5
|
+
createPrivateKey,
|
|
6
|
+
createPublicKey,
|
|
7
|
+
randomBytes,
|
|
8
|
+
sign
|
|
9
|
+
} from "node:crypto";
|
|
10
|
+
import { execFile, execFileSync } from "node:child_process";
|
|
11
|
+
import { userInfo } from "node:os";
|
|
12
|
+
import { basename, dirname, isAbsolute, join, normalize, relative } from "node:path";
|
|
13
|
+
|
|
14
|
+
// packages/protocol/src/darwin-thoughts-custody.ts
|
|
15
|
+
var DARWIN_THOUGHTS_CUSTODY_VERSION = 1;
|
|
16
|
+
var DARWIN_THOUGHTS_PRIVATE_ROOT = "/run/catalyst-thoughts-private";
|
|
17
|
+
var DARWIN_THOUGHTS_MAX_EXCHANGE_MS = 30000;
|
|
18
|
+
var DARWIN_THOUGHTS_MAX_PAYLOAD_BYTES = 16384;
|
|
19
|
+
var AUTHORITY_FIELDS = [
|
|
20
|
+
"version",
|
|
21
|
+
"installationId",
|
|
22
|
+
"publicKey",
|
|
23
|
+
"nativeUid",
|
|
24
|
+
"nativeGid",
|
|
25
|
+
"nativeHome",
|
|
26
|
+
"endpoint",
|
|
27
|
+
"daemonId",
|
|
28
|
+
"thoughtsRoot",
|
|
29
|
+
"locksRoot",
|
|
30
|
+
"requestsRoot",
|
|
31
|
+
"responsesRoot"
|
|
32
|
+
];
|
|
33
|
+
var REQUEST_FIELDS = [
|
|
34
|
+
"version",
|
|
35
|
+
"installationId",
|
|
36
|
+
"nonce",
|
|
37
|
+
"tenant",
|
|
38
|
+
"repository",
|
|
39
|
+
"issuedAtMs",
|
|
40
|
+
"expiresAtMs"
|
|
41
|
+
];
|
|
42
|
+
var RESPONSE_FIELDS = [
|
|
43
|
+
"version",
|
|
44
|
+
"kind",
|
|
45
|
+
"authority",
|
|
46
|
+
"request",
|
|
47
|
+
"observedAtMs",
|
|
48
|
+
"expiresAtMs",
|
|
49
|
+
"nativeBootId",
|
|
50
|
+
"producerInstance"
|
|
51
|
+
];
|
|
52
|
+
var STAT_FIELDS = [
|
|
53
|
+
"path",
|
|
54
|
+
"device",
|
|
55
|
+
"inode",
|
|
56
|
+
"uid",
|
|
57
|
+
"gid",
|
|
58
|
+
"mode",
|
|
59
|
+
"isDirectory",
|
|
60
|
+
"isSymbolicLink"
|
|
61
|
+
];
|
|
62
|
+
var HEX_NONCE = /^[a-f0-9]{64}$/;
|
|
63
|
+
var TOKEN = /^[A-Za-z0-9][A-Za-z0-9_.:-]{0,127}$/;
|
|
64
|
+
var TENANT = /^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$/;
|
|
65
|
+
var OWNER = /^[A-Za-z0-9][A-Za-z0-9_.-]{0,99}$/;
|
|
66
|
+
var REPOSITORY = /^[A-Za-z0-9_.-]{1,100}$/;
|
|
67
|
+
var PUBLIC_KEY = /^MCowBQYDK2VwAyEA[A-Za-z0-9+/]{42}[AEIMQUYcgkosw048]=$/;
|
|
68
|
+
var UINT64_MAX = "18446744073709551615";
|
|
69
|
+
var MAX_PATH_CHARS = 4096;
|
|
70
|
+
function invalid(reason) {
|
|
71
|
+
throw new Error(reason);
|
|
72
|
+
}
|
|
73
|
+
function plainRecord(value) {
|
|
74
|
+
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
75
|
+
return false;
|
|
76
|
+
const prototype = Object.getPrototypeOf(value);
|
|
77
|
+
return prototype === null || prototype === Object.prototype;
|
|
78
|
+
}
|
|
79
|
+
function exact(value, required, optional, reason) {
|
|
80
|
+
if (!plainRecord(value))
|
|
81
|
+
return invalid(reason);
|
|
82
|
+
const fields = Reflect.ownKeys(value);
|
|
83
|
+
if (required.some((key) => !Object.hasOwn(value, key)) || fields.some((key) => typeof key !== "string" || !required.includes(key) && !optional.includes(key)) || fields.some((key) => !Object.hasOwn(Object.getOwnPropertyDescriptor(value, key) ?? {}, "value")))
|
|
84
|
+
return invalid(reason);
|
|
85
|
+
return value;
|
|
86
|
+
}
|
|
87
|
+
function matches(value, pattern, reason) {
|
|
88
|
+
if (typeof value !== "string" || !pattern.test(value))
|
|
89
|
+
return invalid(reason);
|
|
90
|
+
return value;
|
|
91
|
+
}
|
|
92
|
+
function integer(value, min, max, reason) {
|
|
93
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < min || value > max)
|
|
94
|
+
return invalid(reason);
|
|
95
|
+
return value;
|
|
96
|
+
}
|
|
97
|
+
function path(value, reason) {
|
|
98
|
+
if (typeof value !== "string" || value.length > MAX_PATH_CHARS || !value.startsWith("/") || value === "/" || value.endsWith("/") || Array.from(value).some((character) => character.charCodeAt(0) < 32 || character.charCodeAt(0) === 127) || value.slice(1).split("/").some((part) => part === "" || part === "." || part === ".."))
|
|
99
|
+
return invalid(reason);
|
|
100
|
+
return value;
|
|
101
|
+
}
|
|
102
|
+
function below(child, parent) {
|
|
103
|
+
return child.startsWith(parent + "/");
|
|
104
|
+
}
|
|
105
|
+
function tuple(tenant, repository, reason) {
|
|
106
|
+
const tenantName = matches(tenant, TENANT, reason);
|
|
107
|
+
if (typeof repository !== "string" || repository.length > 201)
|
|
108
|
+
return invalid(reason);
|
|
109
|
+
const parts = repository.split("/");
|
|
110
|
+
const owner = matches(parts[0], OWNER, reason);
|
|
111
|
+
const name = matches(parts[1], REPOSITORY, reason);
|
|
112
|
+
if (parts.length !== 2 || name === "." || name === "..")
|
|
113
|
+
return invalid(reason);
|
|
114
|
+
return { tenant: tenantName, repository: owner + "/" + name };
|
|
115
|
+
}
|
|
116
|
+
function parseDarwinThoughtsAuthority(value) {
|
|
117
|
+
const reason = "darwin_thoughts_authority_invalid";
|
|
118
|
+
const row = exact(value, AUTHORITY_FIELDS, [], reason);
|
|
119
|
+
if (row.version !== DARWIN_THOUGHTS_CUSTODY_VERSION)
|
|
120
|
+
return invalid(reason);
|
|
121
|
+
const nativeHome = path(row.nativeHome, reason);
|
|
122
|
+
const thoughtsRoot = path(row.thoughtsRoot, reason);
|
|
123
|
+
const locksRoot = path(row.locksRoot, reason);
|
|
124
|
+
const requestsRoot = path(row.requestsRoot, reason);
|
|
125
|
+
const responsesRoot = path(row.responsesRoot, reason);
|
|
126
|
+
const roots = [thoughtsRoot, locksRoot, requestsRoot, responsesRoot];
|
|
127
|
+
if (roots.some((root) => !below(root, nativeHome)) || roots.some((root, index) => roots.some((other, otherIndex) => index !== otherIndex && (root === other || below(root, other) || below(other, root)))))
|
|
128
|
+
return invalid(reason);
|
|
129
|
+
if (typeof row.endpoint !== "string" || !row.endpoint.startsWith("unix://"))
|
|
130
|
+
return invalid(reason);
|
|
131
|
+
const endpoint = "unix://" + path(row.endpoint.slice(7), reason);
|
|
132
|
+
return Object.freeze({
|
|
133
|
+
version: DARWIN_THOUGHTS_CUSTODY_VERSION,
|
|
134
|
+
installationId: matches(row.installationId, HEX_NONCE, reason),
|
|
135
|
+
publicKey: matches(row.publicKey, PUBLIC_KEY, reason),
|
|
136
|
+
nativeUid: integer(row.nativeUid, 1, 4294967294, reason),
|
|
137
|
+
nativeGid: integer(row.nativeGid, 0, 4294967294, reason),
|
|
138
|
+
nativeHome,
|
|
139
|
+
endpoint,
|
|
140
|
+
daemonId: matches(row.daemonId, TOKEN, reason),
|
|
141
|
+
thoughtsRoot,
|
|
142
|
+
locksRoot,
|
|
143
|
+
requestsRoot,
|
|
144
|
+
responsesRoot
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
function deriveDarwinThoughtsTargets(authority, tenant, repository) {
|
|
148
|
+
const installed = parseDarwinThoughtsAuthority(authority);
|
|
149
|
+
const selected = tuple(tenant, repository, "darwin_thoughts_tuple_invalid");
|
|
150
|
+
const suffix = selected.tenant + "/" + selected.repository;
|
|
151
|
+
const privateRoot = DARWIN_THOUGHTS_PRIVATE_ROOT;
|
|
152
|
+
return Object.freeze({
|
|
153
|
+
checkoutSource: installed.thoughtsRoot + "/" + suffix,
|
|
154
|
+
lockSource: installed.locksRoot + "/" + selected.tenant,
|
|
155
|
+
requestSource: installed.requestsRoot + "/" + suffix,
|
|
156
|
+
responseSource: installed.responsesRoot + "/" + suffix,
|
|
157
|
+
publicCheckoutTarget: "/srv/catalyst/tenants/" + selected.tenant + "/repos/" + selected.repository,
|
|
158
|
+
privateCheckoutTarget: privateRoot + "/checkouts/" + suffix,
|
|
159
|
+
privateLockTarget: privateRoot + "/locks/" + selected.tenant,
|
|
160
|
+
privateRequestTarget: privateRoot + "/requests/" + suffix,
|
|
161
|
+
privateResponseTarget: privateRoot + "/responses/" + suffix
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
function parseDarwinThoughtsRequest(value) {
|
|
165
|
+
const reason = "darwin_thoughts_request_invalid";
|
|
166
|
+
const row = exact(value, REQUEST_FIELDS, [], reason);
|
|
167
|
+
if (row.version !== DARWIN_THOUGHTS_CUSTODY_VERSION)
|
|
168
|
+
return invalid(reason);
|
|
169
|
+
const selected = tuple(row.tenant, row.repository, reason);
|
|
170
|
+
const issuedAtMs = integer(row.issuedAtMs, 1, Number.MAX_SAFE_INTEGER, reason);
|
|
171
|
+
const expiresAtMs = integer(row.expiresAtMs, 1, Number.MAX_SAFE_INTEGER, reason);
|
|
172
|
+
if (expiresAtMs <= issuedAtMs || expiresAtMs - issuedAtMs > DARWIN_THOUGHTS_MAX_EXCHANGE_MS)
|
|
173
|
+
return invalid(reason);
|
|
174
|
+
return Object.freeze({
|
|
175
|
+
version: DARWIN_THOUGHTS_CUSTODY_VERSION,
|
|
176
|
+
installationId: matches(row.installationId, HEX_NONCE, reason),
|
|
177
|
+
nonce: matches(row.nonce, HEX_NONCE, reason),
|
|
178
|
+
...selected,
|
|
179
|
+
issuedAtMs,
|
|
180
|
+
expiresAtMs
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
function decimal(value, nonzero, reason) {
|
|
184
|
+
const text = matches(value, /^(0|[1-9][0-9]{0,19})$/, reason);
|
|
185
|
+
if (nonzero && text === "0" || text.length === 20 && text > UINT64_MAX)
|
|
186
|
+
return invalid(reason);
|
|
187
|
+
return text;
|
|
188
|
+
}
|
|
189
|
+
function statIdentity(value, expectedPath, authority, native, git) {
|
|
190
|
+
const reason = "darwin_thoughts_response_invalid";
|
|
191
|
+
const row = exact(value, STAT_FIELDS, [], reason);
|
|
192
|
+
const observedPath = path(row.path, reason);
|
|
193
|
+
const uid = integer(row.uid, 0, 4294967294, reason);
|
|
194
|
+
const gid = integer(row.gid, 0, 4294967294, reason);
|
|
195
|
+
const mode = integer(row.mode, 0, 4095, reason);
|
|
196
|
+
if (observedPath !== expectedPath || row.isDirectory !== true || row.isSymbolicLink !== false || (native ? uid !== authority.nativeUid || gid !== authority.nativeGid : uid !== 0 && uid !== 10001) || (mode & 448) !== 448 || (mode & 3602) !== 0 || !git && (mode & 7) !== 0)
|
|
197
|
+
return invalid(reason);
|
|
198
|
+
return Object.freeze({
|
|
199
|
+
path: observedPath,
|
|
200
|
+
device: decimal(row.device, false, reason),
|
|
201
|
+
inode: decimal(row.inode, true, reason),
|
|
202
|
+
uid,
|
|
203
|
+
gid,
|
|
204
|
+
mode,
|
|
205
|
+
isDirectory: true,
|
|
206
|
+
isSymbolicLink: false
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
function parseDarwinThoughtsResponse(value, context) {
|
|
210
|
+
const reason = "darwin_thoughts_response_invalid";
|
|
211
|
+
try {
|
|
212
|
+
const basic = exact(value, RESPONSE_FIELDS, [
|
|
213
|
+
"nativeCheckout",
|
|
214
|
+
"nativeLock",
|
|
215
|
+
"engineCheckout",
|
|
216
|
+
"engineLock",
|
|
217
|
+
"nativeGit",
|
|
218
|
+
"engineGit",
|
|
219
|
+
"reason"
|
|
220
|
+
], reason);
|
|
221
|
+
if (basic.version !== DARWIN_THOUGHTS_CUSTODY_VERSION)
|
|
222
|
+
return invalid(reason);
|
|
223
|
+
const authority = parseDarwinThoughtsAuthority(basic.authority);
|
|
224
|
+
const request = parseDarwinThoughtsRequest(basic.request);
|
|
225
|
+
const installed = parseDarwinThoughtsAuthority(context.authority);
|
|
226
|
+
const expected = parseDarwinThoughtsRequest(context.request);
|
|
227
|
+
const nowMs = integer(context.nowMs, 1, Number.MAX_SAFE_INTEGER, reason);
|
|
228
|
+
if (canonicalDarwinThoughtsJson(authority) !== canonicalDarwinThoughtsJson(installed) || canonicalDarwinThoughtsJson(request) !== canonicalDarwinThoughtsJson(expected) || request.installationId !== authority.installationId || request.issuedAtMs > nowMs || request.expiresAtMs <= nowMs)
|
|
229
|
+
return invalid(reason);
|
|
230
|
+
const observedAtMs = integer(basic.observedAtMs, 1, Number.MAX_SAFE_INTEGER, reason);
|
|
231
|
+
const expiresAtMs = integer(basic.expiresAtMs, 1, Number.MAX_SAFE_INTEGER, reason);
|
|
232
|
+
if (observedAtMs < request.issuedAtMs || observedAtMs > nowMs || expiresAtMs <= observedAtMs || expiresAtMs <= nowMs || expiresAtMs > request.expiresAtMs)
|
|
233
|
+
return invalid(reason);
|
|
234
|
+
const binding = {
|
|
235
|
+
version: DARWIN_THOUGHTS_CUSTODY_VERSION,
|
|
236
|
+
authority,
|
|
237
|
+
request,
|
|
238
|
+
observedAtMs,
|
|
239
|
+
expiresAtMs,
|
|
240
|
+
nativeBootId: matches(basic.nativeBootId, TOKEN, reason),
|
|
241
|
+
producerInstance: matches(basic.producerInstance, HEX_NONCE, reason)
|
|
242
|
+
};
|
|
243
|
+
if (basic.kind === "refused") {
|
|
244
|
+
const row = exact(value, [...RESPONSE_FIELDS, "reason"], [], reason);
|
|
245
|
+
return Object.freeze({
|
|
246
|
+
...binding,
|
|
247
|
+
kind: "refused",
|
|
248
|
+
reason: matches(row.reason, /^[a-z][a-z0-9_]{0,127}$/, reason)
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
if (basic.kind !== "accepted")
|
|
252
|
+
return invalid(reason);
|
|
253
|
+
const row = exact(value, [...RESPONSE_FIELDS, "nativeCheckout", "nativeLock", "engineCheckout", "engineLock"], ["nativeGit", "engineGit"], reason);
|
|
254
|
+
const paths = deriveDarwinThoughtsTargets(authority, request.tenant, request.repository);
|
|
255
|
+
const hasGit = Object.hasOwn(row, "nativeGit");
|
|
256
|
+
if (hasGit !== Object.hasOwn(row, "engineGit"))
|
|
257
|
+
return invalid(reason);
|
|
258
|
+
return Object.freeze({
|
|
259
|
+
...binding,
|
|
260
|
+
kind: "accepted",
|
|
261
|
+
nativeCheckout: statIdentity(row.nativeCheckout, paths.checkoutSource, authority, true, false),
|
|
262
|
+
nativeLock: statIdentity(row.nativeLock, paths.lockSource, authority, true, false),
|
|
263
|
+
engineCheckout: statIdentity(row.engineCheckout, paths.checkoutSource, authority, false, false),
|
|
264
|
+
engineLock: statIdentity(row.engineLock, paths.lockSource, authority, false, false),
|
|
265
|
+
...hasGit ? {
|
|
266
|
+
nativeGit: statIdentity(row.nativeGit, paths.checkoutSource + "/.git", authority, true, true),
|
|
267
|
+
engineGit: statIdentity(row.engineGit, paths.checkoutSource + "/.git", authority, false, true)
|
|
268
|
+
} : {}
|
|
269
|
+
});
|
|
270
|
+
} catch {
|
|
271
|
+
return invalid(reason);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
function utf8Bytes(text, reason) {
|
|
275
|
+
let size = 0;
|
|
276
|
+
for (let index = 0;index < text.length; index++) {
|
|
277
|
+
const char = text.charCodeAt(index);
|
|
278
|
+
if (char <= 127)
|
|
279
|
+
size++;
|
|
280
|
+
else if (char <= 2047)
|
|
281
|
+
size += 2;
|
|
282
|
+
else if (char >= 55296 && char <= 56319) {
|
|
283
|
+
const next = text.charCodeAt(++index);
|
|
284
|
+
if (!(next >= 56320 && next <= 57343))
|
|
285
|
+
return invalid(reason);
|
|
286
|
+
size += 4;
|
|
287
|
+
} else {
|
|
288
|
+
if (char >= 56320 && char <= 57343)
|
|
289
|
+
return invalid(reason);
|
|
290
|
+
size += 3;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
return size;
|
|
294
|
+
}
|
|
295
|
+
function canonicalDarwinThoughtsJson(value) {
|
|
296
|
+
const reason = "darwin_thoughts_json_invalid";
|
|
297
|
+
const seen = new Set;
|
|
298
|
+
let nodes = 0;
|
|
299
|
+
function encode(input, depth) {
|
|
300
|
+
if (++nodes > 4096 || depth > 16)
|
|
301
|
+
return invalid(reason);
|
|
302
|
+
if (input === null)
|
|
303
|
+
return "null";
|
|
304
|
+
if (typeof input === "boolean")
|
|
305
|
+
return input ? "true" : "false";
|
|
306
|
+
if (typeof input === "number") {
|
|
307
|
+
if (!Number.isSafeInteger(input) || Object.is(input, -0))
|
|
308
|
+
return invalid(reason);
|
|
309
|
+
return String(input);
|
|
310
|
+
}
|
|
311
|
+
if (typeof input === "string") {
|
|
312
|
+
if (utf8Bytes(input, reason) > DARWIN_THOUGHTS_MAX_PAYLOAD_BYTES)
|
|
313
|
+
return invalid(reason);
|
|
314
|
+
return JSON.stringify(input);
|
|
315
|
+
}
|
|
316
|
+
if (typeof input !== "object" || seen.has(input))
|
|
317
|
+
return invalid(reason);
|
|
318
|
+
seen.add(input);
|
|
319
|
+
try {
|
|
320
|
+
if (Array.isArray(input)) {
|
|
321
|
+
const keys = Reflect.ownKeys(input);
|
|
322
|
+
if (input.length > 4096 || Object.keys(input).length !== input.length || keys.length !== input.length + 1 || keys.some((key) => key !== "length" && (typeof key !== "string" || !/^(0|[1-9][0-9]*)$/.test(key) || !Object.hasOwn(Object.getOwnPropertyDescriptor(input, key) ?? {}, "value"))))
|
|
323
|
+
return invalid(reason);
|
|
324
|
+
return "[" + input.map((item) => encode(item, depth + 1)).join(",") + "]";
|
|
325
|
+
}
|
|
326
|
+
if (!plainRecord(input) || Reflect.ownKeys(input).some((key) => typeof key !== "string"))
|
|
327
|
+
return invalid(reason);
|
|
328
|
+
const keys = Object.keys(input).sort();
|
|
329
|
+
if (keys.length > 4096 || Reflect.ownKeys(input).length !== keys.length)
|
|
330
|
+
return invalid(reason);
|
|
331
|
+
return "{" + keys.map((key) => {
|
|
332
|
+
const descriptor = Object.getOwnPropertyDescriptor(input, key);
|
|
333
|
+
if (!descriptor || !Object.hasOwn(descriptor, "value"))
|
|
334
|
+
return invalid(reason);
|
|
335
|
+
return JSON.stringify(key) + ":" + encode(descriptor.value, depth + 1);
|
|
336
|
+
}).join(",") + "}";
|
|
337
|
+
} finally {
|
|
338
|
+
seen.delete(input);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
const result = encode(value, 0);
|
|
342
|
+
if (utf8Bytes(result, reason) > DARWIN_THOUGHTS_MAX_PAYLOAD_BYTES)
|
|
343
|
+
return invalid(reason);
|
|
344
|
+
return result;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// packages/host-isolation/src/darwin-thoughts-producer.ts
|
|
348
|
+
class ProducerError extends Error {
|
|
349
|
+
reason;
|
|
350
|
+
constructor(reason) {
|
|
351
|
+
super("darwin_thoughts_producer:" + reason);
|
|
352
|
+
this.reason = reason;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
function fail(reason) {
|
|
356
|
+
throw new ProducerError(reason);
|
|
357
|
+
}
|
|
358
|
+
function canonicalPath(value, reason) {
|
|
359
|
+
if (typeof value !== "string" || !isAbsolute(value) || normalize(value) !== value || value === "/" || value.includes("\x00") || value.endsWith("/"))
|
|
360
|
+
fail(reason);
|
|
361
|
+
return value;
|
|
362
|
+
}
|
|
363
|
+
function below2(path, root) {
|
|
364
|
+
const rel = relative(root, path);
|
|
365
|
+
return rel !== "" && rel !== ".." && !rel.startsWith("../") && !isAbsolute(rel);
|
|
366
|
+
}
|
|
367
|
+
function within(path, root) {
|
|
368
|
+
return path === root || below2(path, root);
|
|
369
|
+
}
|
|
370
|
+
function exact2(value, fields, reason) {
|
|
371
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
372
|
+
fail(reason);
|
|
373
|
+
const row = value;
|
|
374
|
+
if (Object.keys(row).length !== fields.length || fields.some((key) => !Object.hasOwn(row, key)))
|
|
375
|
+
fail(reason);
|
|
376
|
+
return row;
|
|
377
|
+
}
|
|
378
|
+
function parseCanonical(bytes, reason) {
|
|
379
|
+
try {
|
|
380
|
+
const text = bytes.toString("utf8");
|
|
381
|
+
const value = JSON.parse(text);
|
|
382
|
+
if (!Buffer.from(text).equals(bytes) || canonicalDarwinThoughtsJson(value) !== text)
|
|
383
|
+
fail(reason);
|
|
384
|
+
return value;
|
|
385
|
+
} catch {
|
|
386
|
+
return fail(reason);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
function same(a, b) {
|
|
390
|
+
return a.dev === b.dev && a.ino === b.ino && a.uid === b.uid && a.gid === b.gid && a.mode === b.mode && (a.isDirectory() || a.nlink === b.nlink);
|
|
391
|
+
}
|
|
392
|
+
function unlinkIfPresent(path) {
|
|
393
|
+
try {
|
|
394
|
+
fs.unlinkSync(path);
|
|
395
|
+
} catch (error) {
|
|
396
|
+
if (error.code !== "ENOENT")
|
|
397
|
+
throw error;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
class NativeCustody {
|
|
402
|
+
principal;
|
|
403
|
+
ports;
|
|
404
|
+
held = new Map;
|
|
405
|
+
constructor(principal, ports) {
|
|
406
|
+
this.principal = principal;
|
|
407
|
+
this.ports = ports;
|
|
408
|
+
}
|
|
409
|
+
stat(path, stat) {
|
|
410
|
+
return this.ports.nativeStat(path, stat);
|
|
411
|
+
}
|
|
412
|
+
hold(path, directory, privateMode = false, publicGit = false) {
|
|
413
|
+
const prior = this.held.get(path);
|
|
414
|
+
if (prior) {
|
|
415
|
+
this.recheckOne(prior);
|
|
416
|
+
this.policy(prior.stat, directory, privateMode, publicGit);
|
|
417
|
+
return prior;
|
|
418
|
+
}
|
|
419
|
+
let fd;
|
|
420
|
+
try {
|
|
421
|
+
fd = fs.openSync(path, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW | fs.constants.O_NONBLOCK | (directory ? fs.constants.O_DIRECTORY : 0));
|
|
422
|
+
} catch {
|
|
423
|
+
return fail(directory ? "native_path_unsafe" : "private_file_custody_unsafe");
|
|
424
|
+
}
|
|
425
|
+
try {
|
|
426
|
+
const stat = this.stat(path, fs.fstatSync(fd, { bigint: true }));
|
|
427
|
+
this.policy(stat, directory, privateMode, publicGit);
|
|
428
|
+
const held = { path, fd, stat };
|
|
429
|
+
this.recheckOne(held);
|
|
430
|
+
this.held.set(path, held);
|
|
431
|
+
return held;
|
|
432
|
+
} catch (error) {
|
|
433
|
+
fs.closeSync(fd);
|
|
434
|
+
throw error;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
policy(stat, directory, privateMode, publicGit) {
|
|
438
|
+
if (stat.uid !== BigInt(this.principal.uid) || stat.gid !== BigInt(this.principal.gid))
|
|
439
|
+
fail("native_owner_mismatch");
|
|
440
|
+
const mode = Number(stat.mode & 0o7777n);
|
|
441
|
+
if (directory ? !stat.isDirectory() : !stat.isFile() || stat.nlink !== 1n)
|
|
442
|
+
fail("native_path_unsafe");
|
|
443
|
+
if (directory) {
|
|
444
|
+
if ((mode & 3602) !== 0 || (mode & 448) !== 448 || !publicGit && (mode & 7) !== 0 || privateMode && mode !== 448)
|
|
445
|
+
fail("native_directory_custody_unsafe");
|
|
446
|
+
} else if (mode !== 384)
|
|
447
|
+
fail("private_file_custody_unsafe");
|
|
448
|
+
}
|
|
449
|
+
walk(path, create = false, mode = 488, privateLeaf = false, publicGit = false) {
|
|
450
|
+
canonicalPath(path, "native_path_unsafe");
|
|
451
|
+
if (!within(path, this.principal.home))
|
|
452
|
+
fail("native_path_outside_home");
|
|
453
|
+
this.hold(this.principal.home, true, false, true);
|
|
454
|
+
let cursor = this.principal.home;
|
|
455
|
+
const components = relative(cursor, path).split("/").filter(Boolean);
|
|
456
|
+
for (let index = 0;index < components.length; index++) {
|
|
457
|
+
this.recheck();
|
|
458
|
+
cursor = join(cursor, components[index]);
|
|
459
|
+
let missing = false;
|
|
460
|
+
try {
|
|
461
|
+
fs.lstatSync(cursor);
|
|
462
|
+
} catch (error) {
|
|
463
|
+
if (error.code !== "ENOENT")
|
|
464
|
+
fail("native_path_unsafe");
|
|
465
|
+
missing = true;
|
|
466
|
+
}
|
|
467
|
+
if (missing) {
|
|
468
|
+
if (!create)
|
|
469
|
+
fail("native_path_missing");
|
|
470
|
+
try {
|
|
471
|
+
fs.mkdirSync(cursor, { mode });
|
|
472
|
+
} catch (error) {
|
|
473
|
+
if (error.code !== "EEXIST")
|
|
474
|
+
fail("native_path_create_failed");
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
this.hold(cursor, true, privateLeaf && index === components.length - 1, index < components.length - 1 || publicGit);
|
|
478
|
+
}
|
|
479
|
+
return this.hold(path, true, privateLeaf, publicGit || path === this.principal.home);
|
|
480
|
+
}
|
|
481
|
+
read(path, max) {
|
|
482
|
+
this.walk(dirname(path));
|
|
483
|
+
const held = this.hold(path, false);
|
|
484
|
+
if (held.stat.size > BigInt(max))
|
|
485
|
+
fail("private_file_custody_unsafe");
|
|
486
|
+
const bytes = Buffer.alloc(Number(held.stat.size));
|
|
487
|
+
if (fs.readSync(held.fd, bytes, 0, bytes.length, 0) !== bytes.length)
|
|
488
|
+
fail("private_file_custody_unsafe");
|
|
489
|
+
this.recheckOne(held);
|
|
490
|
+
const after = this.stat(path, fs.fstatSync(held.fd, { bigint: true }));
|
|
491
|
+
if (after.size !== held.stat.size || after.mtimeNs !== held.stat.mtimeNs || after.ctimeNs !== held.stat.ctimeNs)
|
|
492
|
+
fail("private_file_descriptor_changed");
|
|
493
|
+
return bytes;
|
|
494
|
+
}
|
|
495
|
+
recheckOne(held) {
|
|
496
|
+
try {
|
|
497
|
+
const current = this.stat(held.path, fs.fstatSync(held.fd, { bigint: true }));
|
|
498
|
+
const named = this.stat(held.path, fs.lstatSync(held.path, { bigint: true }));
|
|
499
|
+
if (!same(held.stat, current) || !same(current, named))
|
|
500
|
+
fail("native_descriptor_changed");
|
|
501
|
+
if (held.stat.isFile() && (current.size !== held.stat.size || current.mtimeNs !== held.stat.mtimeNs || current.ctimeNs !== held.stat.ctimeNs))
|
|
502
|
+
fail("native_descriptor_changed");
|
|
503
|
+
} catch {
|
|
504
|
+
fail("native_descriptor_changed");
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
recheck() {
|
|
508
|
+
for (const held of this.held.values())
|
|
509
|
+
this.recheckOne(held);
|
|
510
|
+
}
|
|
511
|
+
drop(path) {
|
|
512
|
+
const held = this.held.get(path);
|
|
513
|
+
if (held) {
|
|
514
|
+
this.recheckOne(held);
|
|
515
|
+
fs.closeSync(held.fd);
|
|
516
|
+
this.held.delete(path);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
gitMetadata(path, deadlineMs) {
|
|
520
|
+
const entries = new Map;
|
|
521
|
+
const receive = (entry) => {
|
|
522
|
+
const stat = this.stat(entry, fs.lstatSync(entry, { bigint: true }));
|
|
523
|
+
const mode = Number(stat.mode & 0o7777n);
|
|
524
|
+
if (stat.uid !== BigInt(this.principal.uid) || stat.gid !== BigInt(this.principal.gid) || (mode & 3602) !== 0 || !stat.isDirectory() && (!stat.isFile() || stat.nlink !== 1n))
|
|
525
|
+
fail("native_custody_unavailable");
|
|
526
|
+
return stat;
|
|
527
|
+
};
|
|
528
|
+
const walk = (directory, depth) => {
|
|
529
|
+
if (depth > 32 || this.ports.now() >= deadlineMs)
|
|
530
|
+
fail("native_git_metadata_bounds");
|
|
531
|
+
const before = receive(directory);
|
|
532
|
+
if (!before.isDirectory())
|
|
533
|
+
fail("native_custody_unavailable");
|
|
534
|
+
entries.set(directory, before);
|
|
535
|
+
for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
|
|
536
|
+
if (entries.size >= 16384 || this.ports.now() >= deadlineMs)
|
|
537
|
+
fail("native_git_metadata_bounds");
|
|
538
|
+
const child = join(directory, entry.name);
|
|
539
|
+
const stat = receive(child);
|
|
540
|
+
entries.set(child, stat);
|
|
541
|
+
if (stat.isDirectory())
|
|
542
|
+
walk(child, depth + 1);
|
|
543
|
+
}
|
|
544
|
+
if (!same(before, receive(directory)))
|
|
545
|
+
fail("native_git_metadata_changed");
|
|
546
|
+
};
|
|
547
|
+
walk(path, 0);
|
|
548
|
+
return () => {
|
|
549
|
+
this.recheck();
|
|
550
|
+
for (const [entry, before] of entries) {
|
|
551
|
+
if (this.ports.now() >= deadlineMs)
|
|
552
|
+
fail("native_git_metadata_bounds");
|
|
553
|
+
const after = receive(entry);
|
|
554
|
+
if (!same(before, after) || !before.isDirectory() && (before.size !== after.size || before.mtimeNs !== after.mtimeNs || before.ctimeNs !== after.ctimeNs))
|
|
555
|
+
fail("native_git_metadata_changed");
|
|
556
|
+
}
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
retireExpiredRequest(path) {
|
|
560
|
+
const held = this.hold(path, false);
|
|
561
|
+
this.recheck();
|
|
562
|
+
fs.unlinkSync(path);
|
|
563
|
+
this.held.delete(path);
|
|
564
|
+
fs.closeSync(held.fd);
|
|
565
|
+
}
|
|
566
|
+
close() {
|
|
567
|
+
for (const held of this.held.values())
|
|
568
|
+
fs.closeSync(held.fd);
|
|
569
|
+
this.held.clear();
|
|
570
|
+
}
|
|
571
|
+
identity(held) {
|
|
572
|
+
return {
|
|
573
|
+
path: held.path,
|
|
574
|
+
device: held.stat.dev.toString(),
|
|
575
|
+
inode: held.stat.ino.toString(),
|
|
576
|
+
uid: Number(held.stat.uid),
|
|
577
|
+
gid: Number(held.stat.gid),
|
|
578
|
+
mode: Number(held.stat.mode & 0o7777n),
|
|
579
|
+
isDirectory: true,
|
|
580
|
+
isSymbolicLink: false
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
function installed(configPath, ports) {
|
|
585
|
+
const principal = ports.principal();
|
|
586
|
+
if (principal.platform !== "darwin" || principal.uid <= 0 || principal.euid !== principal.uid || !Number.isSafeInteger(principal.uid) || !Number.isSafeInteger(principal.gid))
|
|
587
|
+
fail("native_principal_invalid");
|
|
588
|
+
canonicalPath(principal.home, "native_principal_home_invalid");
|
|
589
|
+
canonicalPath(configPath, "config_path_unsafe");
|
|
590
|
+
if (!below2(configPath, principal.home))
|
|
591
|
+
fail("native_principal_home_authority_mismatch");
|
|
592
|
+
const custody = new NativeCustody(principal, ports);
|
|
593
|
+
try {
|
|
594
|
+
custody.walk(dirname(configPath), false, 448, true);
|
|
595
|
+
const row = exact2(parseCanonical(custody.read(configPath, 16384), "config_invalid"), [
|
|
596
|
+
"version",
|
|
597
|
+
"authority",
|
|
598
|
+
"privateKeyFile",
|
|
599
|
+
"supervisorImage",
|
|
600
|
+
"dockerExecutable",
|
|
601
|
+
"dockerExecutableSha256"
|
|
602
|
+
], "config_invalid");
|
|
603
|
+
if (row.version !== 1)
|
|
604
|
+
fail("config_invalid");
|
|
605
|
+
const authority = parseDarwinThoughtsAuthority(row.authority);
|
|
606
|
+
if (authority.nativeUid !== principal.uid || authority.nativeGid !== principal.gid || authority.nativeHome !== principal.home)
|
|
607
|
+
fail("native_principal_authority_mismatch");
|
|
608
|
+
const privateKeyFile = canonicalPath(row.privateKeyFile, "key_path_unsafe");
|
|
609
|
+
const dockerExecutable = canonicalPath(row.dockerExecutable, "config_executable_unsafe");
|
|
610
|
+
if (typeof row.supervisorImage !== "string" || !/^[a-z0-9][a-z0-9./:_-]*@sha256:[a-f0-9]{64}$/.test(row.supervisorImage) || typeof row.dockerExecutableSha256 !== "string" || !/^[a-f0-9]{64}$/.test(row.dockerExecutableSha256))
|
|
611
|
+
fail("config_invalid");
|
|
612
|
+
for (const root of [
|
|
613
|
+
authority.thoughtsRoot,
|
|
614
|
+
authority.locksRoot,
|
|
615
|
+
authority.requestsRoot,
|
|
616
|
+
authority.responsesRoot
|
|
617
|
+
]) {
|
|
618
|
+
if (within(configPath, root) || within(privateKeyFile, root) || within(dockerExecutable, root) || within(dirname(configPath), root) || within(root, dirname(configPath)) || within(dirname(privateKeyFile), root) || within(root, dirname(privateKeyFile)))
|
|
619
|
+
fail("key_mount_path_unsafe");
|
|
620
|
+
custody.walk(root, false, 448, root === authority.requestsRoot || root === authority.responsesRoot);
|
|
621
|
+
}
|
|
622
|
+
if (dirname(privateKeyFile) !== dirname(configPath))
|
|
623
|
+
fail("key_state_directory_mismatch");
|
|
624
|
+
let key;
|
|
625
|
+
try {
|
|
626
|
+
key = createPrivateKey({
|
|
627
|
+
key: custody.read(privateKeyFile, 4096),
|
|
628
|
+
type: "pkcs8",
|
|
629
|
+
format: "der"
|
|
630
|
+
});
|
|
631
|
+
} catch {
|
|
632
|
+
return fail("key_custody_invalid");
|
|
633
|
+
}
|
|
634
|
+
if (key.asymmetricKeyType !== "ed25519" || createPublicKey(key).export({ type: "spki", format: "der" }).toString("base64") !== authority.publicKey)
|
|
635
|
+
fail("key_authority_mismatch");
|
|
636
|
+
const config = {
|
|
637
|
+
version: 1,
|
|
638
|
+
authority,
|
|
639
|
+
privateKeyFile,
|
|
640
|
+
dockerExecutable,
|
|
641
|
+
supervisorImage: row.supervisorImage,
|
|
642
|
+
dockerExecutableSha256: row.dockerExecutableSha256
|
|
643
|
+
};
|
|
644
|
+
custody.recheck();
|
|
645
|
+
return { config, principal, custody, key, configPath };
|
|
646
|
+
} catch (error) {
|
|
647
|
+
custody.close();
|
|
648
|
+
throw error;
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
function registryFile(state) {
|
|
652
|
+
return join(dirname(state.configPath), "known-tuples.json");
|
|
653
|
+
}
|
|
654
|
+
function readRegistry(state) {
|
|
655
|
+
const path = registryFile(state);
|
|
656
|
+
try {
|
|
657
|
+
fs.lstatSync(path);
|
|
658
|
+
} catch (error) {
|
|
659
|
+
if (error.code === "ENOENT")
|
|
660
|
+
return [];
|
|
661
|
+
throw error;
|
|
662
|
+
}
|
|
663
|
+
const row = exact2(parseCanonical(state.custody.read(path, 16384), "registry_invalid"), ["version", "installationId", "tuples"], "registry_invalid");
|
|
664
|
+
if (row.version !== 1 || row.installationId !== state.config.authority.installationId || !Array.isArray(row.tuples) || row.tuples.length > 128)
|
|
665
|
+
fail("registry_invalid");
|
|
666
|
+
const seen = new Set;
|
|
667
|
+
return row.tuples.map((value) => {
|
|
668
|
+
const tuple = exact2(value, ["tenant", "repository"], "registry_invalid");
|
|
669
|
+
if (typeof tuple.tenant !== "string" || typeof tuple.repository !== "string")
|
|
670
|
+
fail("registry_invalid");
|
|
671
|
+
deriveDarwinThoughtsTargets(state.config.authority, tuple.tenant, tuple.repository);
|
|
672
|
+
const id = tuple.tenant + "/" + tuple.repository;
|
|
673
|
+
if (seen.has(id))
|
|
674
|
+
fail("registry_invalid");
|
|
675
|
+
seen.add(id);
|
|
676
|
+
return { tenant: tuple.tenant, repository: tuple.repository };
|
|
677
|
+
});
|
|
678
|
+
}
|
|
679
|
+
function writePrivate(state, path, bytes, replace, beforePublish) {
|
|
680
|
+
state.custody.walk(dirname(path), false, 448, true);
|
|
681
|
+
state.custody.recheck();
|
|
682
|
+
if (!replace) {
|
|
683
|
+
try {
|
|
684
|
+
fs.lstatSync(path);
|
|
685
|
+
fail("response_already_exists");
|
|
686
|
+
} catch (error) {
|
|
687
|
+
if (error.code !== "ENOENT")
|
|
688
|
+
throw error;
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
const temporary = join(dirname(path), ".producer-" + randomBytes(16).toString("hex"));
|
|
692
|
+
const fd = fs.openSync(temporary, fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_EXCL | fs.constants.O_NOFOLLOW, 384);
|
|
693
|
+
try {
|
|
694
|
+
fs.writeFileSync(fd, bytes);
|
|
695
|
+
fs.fsyncSync(fd);
|
|
696
|
+
state.custody.recheck();
|
|
697
|
+
beforePublish?.();
|
|
698
|
+
if (replace)
|
|
699
|
+
fs.renameSync(temporary, path);
|
|
700
|
+
else {
|
|
701
|
+
fs.linkSync(temporary, path);
|
|
702
|
+
fs.unlinkSync(temporary);
|
|
703
|
+
}
|
|
704
|
+
const parentFd = fs.openSync(dirname(path), fs.constants.O_RDONLY | fs.constants.O_DIRECTORY | fs.constants.O_NOFOLLOW);
|
|
705
|
+
try {
|
|
706
|
+
fs.fsyncSync(parentFd);
|
|
707
|
+
} finally {
|
|
708
|
+
fs.closeSync(parentFd);
|
|
709
|
+
}
|
|
710
|
+
} finally {
|
|
711
|
+
fs.closeSync(fd);
|
|
712
|
+
unlinkIfPresent(temporary);
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
function remember(state, request) {
|
|
716
|
+
const tuples = readRegistry(state);
|
|
717
|
+
if (tuples.some((tuple) => tuple.tenant === request.tenant && tuple.repository === request.repository))
|
|
718
|
+
return;
|
|
719
|
+
if (tuples.length >= 128)
|
|
720
|
+
fail("registry_capacity_exceeded");
|
|
721
|
+
tuples.push({ tenant: request.tenant, repository: request.repository });
|
|
722
|
+
state.custody.drop(registryFile(state));
|
|
723
|
+
writePrivate(state, registryFile(state), Buffer.from(canonicalDarwinThoughtsJson({
|
|
724
|
+
version: 1,
|
|
725
|
+
installationId: state.config.authority.installationId,
|
|
726
|
+
tuples
|
|
727
|
+
})), true);
|
|
728
|
+
}
|
|
729
|
+
function fresh(request, deadlineMs, ports) {
|
|
730
|
+
const now = ports.now();
|
|
731
|
+
if (!Number.isSafeInteger(deadlineMs) || deadlineMs <= now || request.issuedAtMs > now || request.expiresAtMs <= now)
|
|
732
|
+
fail("request_deadline_expired");
|
|
733
|
+
}
|
|
734
|
+
function makeProducer(ports) {
|
|
735
|
+
const producerInstance = randomBytes(32).toString("hex");
|
|
736
|
+
async function answer(options) {
|
|
737
|
+
const state = installed(options.configPath, ports);
|
|
738
|
+
try {
|
|
739
|
+
const { authority } = state.config;
|
|
740
|
+
canonicalPath(options.requestFile, "request_path_invalid");
|
|
741
|
+
let request;
|
|
742
|
+
try {
|
|
743
|
+
request = parseDarwinThoughtsRequest(parseCanonical(state.custody.read(options.requestFile, 4096), "request_invalid"));
|
|
744
|
+
} catch {
|
|
745
|
+
return fail("request_invalid");
|
|
746
|
+
}
|
|
747
|
+
fresh(request, options.deadlineMs, ports);
|
|
748
|
+
if (request.installationId !== authority.installationId || basename(options.requestFile) !== request.nonce + ".json")
|
|
749
|
+
fail("request_authority_invalid");
|
|
750
|
+
const targets = deriveDarwinThoughtsTargets(authority, request.tenant, request.repository);
|
|
751
|
+
const global = dirname(options.requestFile) === authority.requestsRoot;
|
|
752
|
+
if (!global && (dirname(options.requestFile) !== targets.requestSource || !readRegistry(state).some((tuple) => tuple.tenant === request.tenant && tuple.repository === request.repository)))
|
|
753
|
+
fail("request_channel_invalid");
|
|
754
|
+
const responseDirectory = global ? authority.responsesRoot : targets.responseSource;
|
|
755
|
+
const deadlineMs = Math.min(options.deadlineMs, request.expiresAtMs);
|
|
756
|
+
let response;
|
|
757
|
+
try {
|
|
758
|
+
const checkout = state.custody.walk(targets.checkoutSource, true);
|
|
759
|
+
const lock = state.custody.walk(targets.lockSource, true);
|
|
760
|
+
state.custody.walk(targets.requestSource, true, 448, true);
|
|
761
|
+
state.custody.walk(targets.responseSource, true, 448, true);
|
|
762
|
+
let git;
|
|
763
|
+
const gitPath = join(targets.checkoutSource, ".git");
|
|
764
|
+
try {
|
|
765
|
+
fs.lstatSync(gitPath);
|
|
766
|
+
git = state.custody.walk(gitPath, false, 488, false, true);
|
|
767
|
+
} catch (error) {
|
|
768
|
+
if (error.code !== "ENOENT")
|
|
769
|
+
throw error;
|
|
770
|
+
}
|
|
771
|
+
const recheckGit = git ? state.custody.gitMetadata(gitPath, deadlineMs) : undefined;
|
|
772
|
+
fresh(request, deadlineMs, ports);
|
|
773
|
+
const engine = await (ports.engine ?? ((input) => observeEngine(state, input, ports)))({
|
|
774
|
+
image: state.config.supervisorImage,
|
|
775
|
+
endpoint: authority.endpoint,
|
|
776
|
+
nonce: request.nonce,
|
|
777
|
+
deadlineMs,
|
|
778
|
+
checkoutPath: targets.checkoutSource,
|
|
779
|
+
lockPath: targets.lockSource
|
|
780
|
+
});
|
|
781
|
+
fresh(request, deadlineMs, ports);
|
|
782
|
+
state.custody.recheck();
|
|
783
|
+
recheckGit?.();
|
|
784
|
+
if (engine.daemonId !== authority.daemonId || engine.endpoint !== authority.endpoint || engine.nonce !== request.nonce || Boolean(engine.git) !== Boolean(git))
|
|
785
|
+
fail("engine_binding_mismatch");
|
|
786
|
+
response = parseDarwinThoughtsResponse({
|
|
787
|
+
version: 1,
|
|
788
|
+
kind: "accepted",
|
|
789
|
+
authority,
|
|
790
|
+
request,
|
|
791
|
+
observedAtMs: ports.now(),
|
|
792
|
+
expiresAtMs: deadlineMs,
|
|
793
|
+
nativeBootId: state.principal.nativeBootId,
|
|
794
|
+
producerInstance,
|
|
795
|
+
nativeCheckout: state.custody.identity(checkout),
|
|
796
|
+
nativeLock: state.custody.identity(lock),
|
|
797
|
+
engineCheckout: engine.checkout,
|
|
798
|
+
engineLock: engine.lock,
|
|
799
|
+
...git ? { nativeGit: state.custody.identity(git), engineGit: engine.git } : {}
|
|
800
|
+
}, { authority, request, nowMs: ports.now() });
|
|
801
|
+
} catch (error) {
|
|
802
|
+
fresh(request, deadlineMs, ports);
|
|
803
|
+
response = parseDarwinThoughtsResponse({
|
|
804
|
+
version: 1,
|
|
805
|
+
kind: "refused",
|
|
806
|
+
authority,
|
|
807
|
+
request,
|
|
808
|
+
observedAtMs: ports.now(),
|
|
809
|
+
expiresAtMs: deadlineMs,
|
|
810
|
+
nativeBootId: state.principal.nativeBootId,
|
|
811
|
+
producerInstance,
|
|
812
|
+
reason: error instanceof ProducerError ? error.reason : "native_custody_refused"
|
|
813
|
+
}, { authority, request, nowMs: ports.now() });
|
|
814
|
+
}
|
|
815
|
+
if (response.kind === "refused")
|
|
816
|
+
state.custody.close();
|
|
817
|
+
const publication = installed(options.configPath, ports);
|
|
818
|
+
try {
|
|
819
|
+
if (canonicalDarwinThoughtsJson(publication.config) !== canonicalDarwinThoughtsJson(state.config))
|
|
820
|
+
fail("authority_descriptor_changed");
|
|
821
|
+
const received = publication.custody.read(options.requestFile, 4096);
|
|
822
|
+
if (!received.equals(Buffer.from(canonicalDarwinThoughtsJson(request))))
|
|
823
|
+
fail("request_descriptor_changed");
|
|
824
|
+
fresh(request, deadlineMs, ports);
|
|
825
|
+
const payload = canonicalDarwinThoughtsJson(response);
|
|
826
|
+
const envelope = canonicalDarwinThoughtsJson({
|
|
827
|
+
version: 1,
|
|
828
|
+
payload,
|
|
829
|
+
signature: sign(null, Buffer.from(payload), publication.key).toString("base64")
|
|
830
|
+
});
|
|
831
|
+
if (response.kind === "accepted")
|
|
832
|
+
remember(publication, request);
|
|
833
|
+
writePrivate(publication, join(responseDirectory, request.nonce + ".json"), Buffer.from(envelope), false, () => {
|
|
834
|
+
fresh(request, deadlineMs, ports);
|
|
835
|
+
if (response.kind === "accepted")
|
|
836
|
+
state.custody.recheck();
|
|
837
|
+
});
|
|
838
|
+
} finally {
|
|
839
|
+
publication.custody.close();
|
|
840
|
+
}
|
|
841
|
+
return {
|
|
842
|
+
kind: response.kind,
|
|
843
|
+
nonce: request.nonce,
|
|
844
|
+
...response.kind === "refused" ? { reason: response.reason } : {}
|
|
845
|
+
};
|
|
846
|
+
} finally {
|
|
847
|
+
state.custody.close();
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
async function runOnce(options) {
|
|
851
|
+
const state = installed(options.configPath, ports);
|
|
852
|
+
const pending = [];
|
|
853
|
+
try {
|
|
854
|
+
if (!Number.isSafeInteger(options.deadlineMs) || options.deadlineMs <= ports.now())
|
|
855
|
+
fail("producer_deadline_expired");
|
|
856
|
+
const authority = state.config.authority;
|
|
857
|
+
const channels = [
|
|
858
|
+
{ request: authority.requestsRoot, response: authority.responsesRoot },
|
|
859
|
+
...readRegistry(state).map((tuple) => {
|
|
860
|
+
const targets = deriveDarwinThoughtsTargets(authority, tuple.tenant, tuple.repository);
|
|
861
|
+
return { request: targets.requestSource, response: targets.responseSource };
|
|
862
|
+
})
|
|
863
|
+
];
|
|
864
|
+
for (const channel of channels) {
|
|
865
|
+
state.custody.walk(channel.request, false, 448, true);
|
|
866
|
+
state.custody.walk(channel.response, false, 448, true);
|
|
867
|
+
const directory = fs.opendirSync(channel.request);
|
|
868
|
+
try {
|
|
869
|
+
let entries = 0;
|
|
870
|
+
let entry;
|
|
871
|
+
while ((entry = directory.readSync()) !== null) {
|
|
872
|
+
if (++entries > 256 || pending.length >= 32)
|
|
873
|
+
fail("request_inbox_capacity_exceeded");
|
|
874
|
+
if (!/^[a-f0-9]{64}\.json$/.test(entry.name))
|
|
875
|
+
continue;
|
|
876
|
+
if (!entry.isFile())
|
|
877
|
+
fail("request_custody_unsafe");
|
|
878
|
+
const requestPath = join(channel.request, entry.name);
|
|
879
|
+
const request = parseDarwinThoughtsRequest(parseCanonical(state.custody.read(requestPath, 4096), "request_invalid"));
|
|
880
|
+
const targets = deriveDarwinThoughtsTargets(state.config.authority, request.tenant, request.repository);
|
|
881
|
+
if (request.installationId !== state.config.authority.installationId || request.nonce + ".json" !== entry.name || channel.request !== state.config.authority.requestsRoot && channel.request !== targets.requestSource)
|
|
882
|
+
fail("request_authority_invalid");
|
|
883
|
+
if (request.expiresAtMs <= ports.now() && request.issuedAtMs <= ports.now()) {
|
|
884
|
+
state.custody.retireExpiredRequest(requestPath);
|
|
885
|
+
continue;
|
|
886
|
+
}
|
|
887
|
+
const response = join(channel.response, entry.name);
|
|
888
|
+
let exists = true;
|
|
889
|
+
try {
|
|
890
|
+
fs.lstatSync(response);
|
|
891
|
+
} catch (error) {
|
|
892
|
+
if (error.code !== "ENOENT")
|
|
893
|
+
throw error;
|
|
894
|
+
exists = false;
|
|
895
|
+
}
|
|
896
|
+
if (exists) {
|
|
897
|
+
state.custody.read(response, 16384);
|
|
898
|
+
continue;
|
|
899
|
+
}
|
|
900
|
+
pending.push(join(channel.request, entry.name));
|
|
901
|
+
}
|
|
902
|
+
} finally {
|
|
903
|
+
directory.closeSync();
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
state.custody.recheck();
|
|
907
|
+
} finally {
|
|
908
|
+
state.custody.close();
|
|
909
|
+
}
|
|
910
|
+
let accepted = 0;
|
|
911
|
+
let refused = 0;
|
|
912
|
+
for (const requestFile of pending) {
|
|
913
|
+
const result = await answer({ ...options, requestFile });
|
|
914
|
+
if (result.kind === "accepted")
|
|
915
|
+
accepted++;
|
|
916
|
+
else
|
|
917
|
+
refused++;
|
|
918
|
+
}
|
|
919
|
+
return { accepted, refused };
|
|
920
|
+
}
|
|
921
|
+
return { answer, runOnce };
|
|
922
|
+
}
|
|
923
|
+
function nativePrincipal() {
|
|
924
|
+
if (process.platform !== "darwin" || !process.getuid || !process.geteuid || !process.getgid)
|
|
925
|
+
fail("native_principal_invalid");
|
|
926
|
+
const nativeHome = fs.realpathSync(userInfo().homedir);
|
|
927
|
+
const boot = execFileSync("/usr/sbin/sysctl", ["-n", "kern.boottime"], {
|
|
928
|
+
cwd: "/",
|
|
929
|
+
env: { PATH: "/usr/bin:/bin:/usr/sbin:/sbin" },
|
|
930
|
+
timeout: 1000,
|
|
931
|
+
maxBuffer: 1024,
|
|
932
|
+
encoding: "utf8",
|
|
933
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
934
|
+
});
|
|
935
|
+
if (!boot.trim())
|
|
936
|
+
fail("native_boot_unavailable");
|
|
937
|
+
return {
|
|
938
|
+
platform: process.platform,
|
|
939
|
+
uid: process.getuid(),
|
|
940
|
+
euid: process.geteuid(),
|
|
941
|
+
gid: process.getgid(),
|
|
942
|
+
home: nativeHome,
|
|
943
|
+
nativeBootId: createHash("sha256").update(boot).digest("hex")
|
|
944
|
+
};
|
|
945
|
+
}
|
|
946
|
+
function runDarwinThoughtsProducerOnce(options) {
|
|
947
|
+
return makeProducer({
|
|
948
|
+
principal: nativePrincipal,
|
|
949
|
+
now: Date.now,
|
|
950
|
+
nativeStat: (_path, stat) => stat
|
|
951
|
+
}).runOnce(options);
|
|
952
|
+
}
|
|
953
|
+
function command(executable, args, deadlineMs, now) {
|
|
954
|
+
const remaining = deadlineMs - now();
|
|
955
|
+
if (remaining <= 0)
|
|
956
|
+
fail("request_deadline_expired");
|
|
957
|
+
return new Promise((resolve, reject) => {
|
|
958
|
+
execFile(executable, args, {
|
|
959
|
+
cwd: "/",
|
|
960
|
+
env: { PATH: "/usr/bin:/bin:/usr/sbin:/sbin" },
|
|
961
|
+
timeout: remaining,
|
|
962
|
+
killSignal: "SIGKILL",
|
|
963
|
+
maxBuffer: 16384,
|
|
964
|
+
encoding: "utf8"
|
|
965
|
+
}, (error, stdout) => error ? reject(new ProducerError("engine_command_refused")) : resolve(stdout.trim()));
|
|
966
|
+
});
|
|
967
|
+
}
|
|
968
|
+
var ENGINE_PROGRAM = [
|
|
969
|
+
'const fs=require("node:fs"),args=JSON.parse(process.argv[1]);',
|
|
970
|
+
'const identity=(alias,path)=>{const s=fs.lstatSync(alias,{bigint:true});if(!s.isDirectory()||s.isSymbolicLink())throw Error("directory");return {path,device:String(s.dev),inode:String(s.ino),uid:Number(s.uid),gid:Number(s.gid),mode:Number(s.mode&4095n),isDirectory:true,isSymbolicLink:false};};',
|
|
971
|
+
"const t=setTimeout(()=>process.exit(124),Math.min(args.remaining,30000));t.unref();",
|
|
972
|
+
'const checkout=identity("/checkout",args.checkoutPath),lock=identity("/lock",args.lockPath);',
|
|
973
|
+
'for(const alias of ["/checkout","/lock"]){const d=alias+"/"+args.challengeDirectory;if(fs.readFileSync(d+"/native","utf8")!==args.nonce)throw Error("challenge");fs.writeFileSync(d+"/engine",args.nonce,{flag:"wx",mode:384});}',
|
|
974
|
+
'let git;try{git=identity("/checkout/.git",args.checkoutPath+"/.git");}catch(e){if(e.code!=="ENOENT")throw e;}',
|
|
975
|
+
"console.log(JSON.stringify({checkout,lock,...(git?{git}:{})}));"
|
|
976
|
+
].join(`
|
|
977
|
+
`);
|
|
978
|
+
async function observeEngine(state, input, ports) {
|
|
979
|
+
const { config, custody } = state;
|
|
980
|
+
const executable = config.dockerExecutable;
|
|
981
|
+
if (fs.realpathSync(executable) !== executable)
|
|
982
|
+
fail("config_executable_unsafe");
|
|
983
|
+
const executableFd = fs.openSync(executable, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW | fs.constants.O_NONBLOCK);
|
|
984
|
+
let executableStat;
|
|
985
|
+
try {
|
|
986
|
+
executableStat = fs.fstatSync(executableFd, { bigint: true });
|
|
987
|
+
if (!executableStat.isFile() || (executableStat.mode & 0o7022n) !== 0n || (executableStat.mode & 0o111n) === 0n || executableStat.uid !== 0n && executableStat.uid !== BigInt(state.principal.uid) || executableStat.size > 256n * 1024n * 1024n || createHash("sha256").update(fs.readFileSync(executableFd)).digest("hex") !== config.dockerExecutableSha256)
|
|
988
|
+
fail("config_executable_unsafe");
|
|
989
|
+
} finally {
|
|
990
|
+
fs.closeSync(executableFd);
|
|
991
|
+
}
|
|
992
|
+
const dockerConfig = fs.mkdtempSync(join(dirname(state.configPath), ".docker-empty-"));
|
|
993
|
+
const challengeDirectory = ".catalyst-custody-" + randomBytes(16).toString("hex");
|
|
994
|
+
const helperName = "catalyst-custody-" + randomBytes(16).toString("hex");
|
|
995
|
+
const helperLabel = "dev.catalystcloud.thoughts-custody-helper";
|
|
996
|
+
const helperToken = config.authority.installationId + ":" + randomBytes(32).toString("hex");
|
|
997
|
+
const scratch = [];
|
|
998
|
+
let id;
|
|
999
|
+
let cachedImage;
|
|
1000
|
+
let createAttempted = false;
|
|
1001
|
+
const receiveHelperIdentity = (output, acknowledgedId) => {
|
|
1002
|
+
let parsed;
|
|
1003
|
+
try {
|
|
1004
|
+
parsed = JSON.parse(output);
|
|
1005
|
+
} catch {
|
|
1006
|
+
return fail("engine_helper_cleanup_unproved");
|
|
1007
|
+
}
|
|
1008
|
+
if (!Array.isArray(parsed) || parsed.length !== 1 || !parsed[0] || typeof parsed[0] !== "object" || Array.isArray(parsed[0]))
|
|
1009
|
+
fail("engine_helper_cleanup_unproved");
|
|
1010
|
+
const row = parsed[0];
|
|
1011
|
+
if (!row.Config || typeof row.Config !== "object" || Array.isArray(row.Config))
|
|
1012
|
+
fail("engine_helper_cleanup_unproved");
|
|
1013
|
+
const receivedConfig = row.Config;
|
|
1014
|
+
if (!receivedConfig.Labels || typeof receivedConfig.Labels !== "object" || Array.isArray(receivedConfig.Labels))
|
|
1015
|
+
fail("engine_helper_cleanup_unproved");
|
|
1016
|
+
const labels = receivedConfig.Labels;
|
|
1017
|
+
if (typeof row.Id !== "string" || !/^[a-f0-9]{64}$/.test(row.Id) || acknowledgedId !== undefined && row.Id !== acknowledgedId || row.Name !== "/" + helperName || row.Image !== cachedImage || receivedConfig.Image !== input.image || labels[helperLabel] !== helperToken)
|
|
1018
|
+
fail("engine_helper_cleanup_unproved");
|
|
1019
|
+
return row.Id;
|
|
1020
|
+
};
|
|
1021
|
+
const docker = async (args, deadlineMs = input.deadlineMs) => {
|
|
1022
|
+
if (!same(executableStat, fs.lstatSync(executable, { bigint: true })))
|
|
1023
|
+
fail("config_executable_changed");
|
|
1024
|
+
custody.recheck();
|
|
1025
|
+
return command(executable, ["--config", dockerConfig, "--host", input.endpoint, ...args], deadlineMs, ports.now);
|
|
1026
|
+
};
|
|
1027
|
+
let result;
|
|
1028
|
+
try {
|
|
1029
|
+
cachedImage = await docker(["image", "inspect", "--format", "{{.Id}}", input.image]);
|
|
1030
|
+
if (!/^sha256:[a-f0-9]{64}$/.test(cachedImage))
|
|
1031
|
+
fail("engine_cached_image_unavailable");
|
|
1032
|
+
const daemonId = await docker(["info", "--format", "{{.ID}}"]);
|
|
1033
|
+
if (daemonId !== config.authority.daemonId)
|
|
1034
|
+
fail("engine_binding_mismatch");
|
|
1035
|
+
for (const root of [input.checkoutPath, input.lockPath]) {
|
|
1036
|
+
custody.recheck();
|
|
1037
|
+
const path = join(root, challengeDirectory);
|
|
1038
|
+
fs.mkdirSync(path, { mode: 448 });
|
|
1039
|
+
scratch.push(path);
|
|
1040
|
+
custody.hold(path, true, true);
|
|
1041
|
+
fs.writeFileSync(join(path, "native"), input.nonce, { flag: "wx", mode: 384 });
|
|
1042
|
+
}
|
|
1043
|
+
const remaining = input.deadlineMs - ports.now();
|
|
1044
|
+
if (remaining <= 0)
|
|
1045
|
+
fail("request_deadline_expired");
|
|
1046
|
+
const helperArgs = canonicalDarwinThoughtsJson({
|
|
1047
|
+
remaining,
|
|
1048
|
+
nonce: input.nonce,
|
|
1049
|
+
challengeDirectory,
|
|
1050
|
+
checkoutPath: input.checkoutPath,
|
|
1051
|
+
lockPath: input.lockPath
|
|
1052
|
+
});
|
|
1053
|
+
createAttempted = true;
|
|
1054
|
+
const created = await docker([
|
|
1055
|
+
"create",
|
|
1056
|
+
"--pull=never",
|
|
1057
|
+
"--name",
|
|
1058
|
+
helperName,
|
|
1059
|
+
"--label",
|
|
1060
|
+
helperLabel + "=" + helperToken,
|
|
1061
|
+
"--network",
|
|
1062
|
+
"none",
|
|
1063
|
+
"--read-only",
|
|
1064
|
+
"--cap-drop",
|
|
1065
|
+
"ALL",
|
|
1066
|
+
"--security-opt",
|
|
1067
|
+
"no-new-privileges",
|
|
1068
|
+
"--user",
|
|
1069
|
+
"10001:10001",
|
|
1070
|
+
"--memory",
|
|
1071
|
+
"64m",
|
|
1072
|
+
"--memory-swap",
|
|
1073
|
+
"64m",
|
|
1074
|
+
"--cpus",
|
|
1075
|
+
"1",
|
|
1076
|
+
"--pids-limit",
|
|
1077
|
+
"32",
|
|
1078
|
+
"--workdir",
|
|
1079
|
+
"/",
|
|
1080
|
+
"--mount",
|
|
1081
|
+
"type=bind,src=" + input.checkoutPath + ",dst=/checkout",
|
|
1082
|
+
"--mount",
|
|
1083
|
+
"type=bind,src=" + input.lockPath + ",dst=/lock",
|
|
1084
|
+
"--entrypoint",
|
|
1085
|
+
"/usr/bin/timeout",
|
|
1086
|
+
input.image,
|
|
1087
|
+
"--signal=KILL",
|
|
1088
|
+
Math.max(1, Math.ceil(remaining / 1000)) + "s",
|
|
1089
|
+
"/usr/bin/env",
|
|
1090
|
+
"-i",
|
|
1091
|
+
"PATH=/usr/bin:/bin",
|
|
1092
|
+
"BUN_RUNTIME_TRANSPILER_CACHE_PATH=0",
|
|
1093
|
+
"/usr/local/bin/bun",
|
|
1094
|
+
"--config=/dev/null",
|
|
1095
|
+
"--no-env-file",
|
|
1096
|
+
"--cwd=/",
|
|
1097
|
+
"-e",
|
|
1098
|
+
ENGINE_PROGRAM,
|
|
1099
|
+
helperArgs
|
|
1100
|
+
]);
|
|
1101
|
+
if (!/^[a-f0-9]{64}$/.test(created))
|
|
1102
|
+
fail("engine_helper_identity_invalid");
|
|
1103
|
+
id = created;
|
|
1104
|
+
id = receiveHelperIdentity(await docker(["inspect", helperName]), id);
|
|
1105
|
+
const output = await docker(["start", "--attach", id]);
|
|
1106
|
+
if (await docker(["inspect", "--format", "{{.State.ExitCode}}", id]) !== "0")
|
|
1107
|
+
fail("engine_helper_refused");
|
|
1108
|
+
custody.recheck();
|
|
1109
|
+
for (const path of scratch) {
|
|
1110
|
+
if (custody.read(join(path, "engine"), 128).toString("utf8") !== input.nonce)
|
|
1111
|
+
fail("engine_challenge_mismatch");
|
|
1112
|
+
}
|
|
1113
|
+
const parsed = JSON.parse(output);
|
|
1114
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
1115
|
+
fail("engine_helper_output_invalid");
|
|
1116
|
+
const row = parsed;
|
|
1117
|
+
if (Object.keys(row).some((key) => !["checkout", "lock", "git"].includes(key)))
|
|
1118
|
+
fail("engine_helper_output_invalid");
|
|
1119
|
+
result = {
|
|
1120
|
+
value: {
|
|
1121
|
+
daemonId,
|
|
1122
|
+
endpoint: input.endpoint,
|
|
1123
|
+
nonce: input.nonce,
|
|
1124
|
+
checkout: row.checkout,
|
|
1125
|
+
lock: row.lock,
|
|
1126
|
+
...Object.hasOwn(row, "git") ? { git: row.git } : {}
|
|
1127
|
+
}
|
|
1128
|
+
};
|
|
1129
|
+
} catch (error) {
|
|
1130
|
+
result = { error };
|
|
1131
|
+
}
|
|
1132
|
+
let cleanupUnproved = false;
|
|
1133
|
+
if (createAttempted) {
|
|
1134
|
+
const cleanupStarted = performance.now();
|
|
1135
|
+
const cleanupDocker = (args) => {
|
|
1136
|
+
const remaining = 2000 - (performance.now() - cleanupStarted);
|
|
1137
|
+
if (remaining <= 0 || !same(executableStat, fs.lstatSync(executable, { bigint: true })))
|
|
1138
|
+
fail("engine_helper_cleanup_unproved");
|
|
1139
|
+
return command(executable, ["--config", dockerConfig, "--host", input.endpoint, ...args], ports.now() + Math.ceil(remaining), ports.now);
|
|
1140
|
+
};
|
|
1141
|
+
try {
|
|
1142
|
+
if (await cleanupDocker(["info", "--format", "{{.ID}}"]) !== config.authority.daemonId || await cleanupDocker(["image", "inspect", "--format", "{{.Id}}", input.image]) !== cachedImage)
|
|
1143
|
+
fail("engine_helper_cleanup_unproved");
|
|
1144
|
+
let received;
|
|
1145
|
+
for (let attempt = 0;attempt < 32; attempt++) {
|
|
1146
|
+
try {
|
|
1147
|
+
received = await cleanupDocker(["inspect", helperName]);
|
|
1148
|
+
break;
|
|
1149
|
+
} catch {
|
|
1150
|
+
if (id !== undefined || performance.now() - cleanupStarted >= 1900)
|
|
1151
|
+
throw new ProducerError("engine_helper_cleanup_unproved");
|
|
1152
|
+
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
if (received === undefined)
|
|
1156
|
+
fail("engine_helper_cleanup_unproved");
|
|
1157
|
+
id = receiveHelperIdentity(received, id);
|
|
1158
|
+
const list = ["ps", "--all", "--no-trunc", "--quiet", "--filter", "id=" + id];
|
|
1159
|
+
if (await cleanupDocker(list) !== id)
|
|
1160
|
+
fail("engine_helper_cleanup_unproved");
|
|
1161
|
+
await cleanupDocker(["rm", "--force", id]);
|
|
1162
|
+
if (await cleanupDocker(list) !== "")
|
|
1163
|
+
fail("engine_helper_cleanup_unproved");
|
|
1164
|
+
} catch {
|
|
1165
|
+
cleanupUnproved = true;
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
for (const path of scratch) {
|
|
1169
|
+
custody.drop(join(path, "engine"));
|
|
1170
|
+
custody.drop(path);
|
|
1171
|
+
for (const file of ["native", "engine"]) {
|
|
1172
|
+
unlinkIfPresent(join(path, file));
|
|
1173
|
+
}
|
|
1174
|
+
fs.rmdirSync(path);
|
|
1175
|
+
}
|
|
1176
|
+
fs.rmdirSync(dockerConfig);
|
|
1177
|
+
if (cleanupUnproved)
|
|
1178
|
+
fail("engine_helper_cleanup_unproved");
|
|
1179
|
+
if ("error" in result)
|
|
1180
|
+
throw result.error;
|
|
1181
|
+
return result.value;
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
// deploy/self-host/darwin-thoughts-custody/producer-entry.ts
|
|
1185
|
+
if (process.argv.length !== 4 || !/^[1-9][0-9]{12,15}$/.test(process.argv[3] ?? "")) {
|
|
1186
|
+
process.stderr.write(`darwin_thoughts_producer:arguments_invalid
|
|
1187
|
+
`);
|
|
1188
|
+
process.exitCode = 1;
|
|
1189
|
+
} else {
|
|
1190
|
+
try {
|
|
1191
|
+
const result = await runDarwinThoughtsProducerOnce({
|
|
1192
|
+
configPath: process.argv[2],
|
|
1193
|
+
deadlineMs: Number(process.argv[3])
|
|
1194
|
+
});
|
|
1195
|
+
process.stdout.write(JSON.stringify(result) + `
|
|
1196
|
+
`);
|
|
1197
|
+
} catch {
|
|
1198
|
+
process.stderr.write(`darwin_thoughts_producer:refused
|
|
1199
|
+
`);
|
|
1200
|
+
process.exitCode = 1;
|
|
1201
|
+
}
|
|
1202
|
+
}
|