@sellable/install 0.1.362 → 0.1.363-phase111.20260720131434
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/bin/sellable-install.mjs +2 -7
- package/lib/sellable-agent/hermes-bridge.mjs +4 -4
- package/lib/sellable-agent/host-worker.mjs +20 -16
- package/lib/sellable-agent/profile-materializer.mjs +2 -2
- package/lib/sellable-agent/provisioning-adapter.mjs +5 -5
- package/lib/sellable-agent/runtime-helper.mjs +10 -107
- package/lib/sellable-agent/service-installer.mjs +0 -2
- package/package.json +1 -3
- package/skill-templates/refill-sends-evergreen.md +5 -0
- package/skill-templates/refill-sends-v2.md +36 -1
- package/skill-templates/refill-sends.md +33 -1
- package/bin/sellable-agent-host-bootstrap.mjs +0 -26
- package/container/Dockerfile +0 -41
- package/container/README.md +0 -15
- package/container/compose.yaml +0 -22
- package/container/entrypoint.sh +0 -17
- package/lib/sellable-agent/external-runtime-builder.mjs +0 -272
- package/lib/sellable-agent/host-bootstrap.mjs +0 -458
|
@@ -1,458 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
createHash,
|
|
3
|
-
createPrivateKey,
|
|
4
|
-
createPublicKey,
|
|
5
|
-
generateKeyPairSync,
|
|
6
|
-
randomUUID,
|
|
7
|
-
} from "node:crypto";
|
|
8
|
-
import {
|
|
9
|
-
chmodSync,
|
|
10
|
-
chownSync,
|
|
11
|
-
closeSync,
|
|
12
|
-
constants,
|
|
13
|
-
existsSync,
|
|
14
|
-
fstatSync,
|
|
15
|
-
fsyncSync,
|
|
16
|
-
lstatSync,
|
|
17
|
-
mkdirSync,
|
|
18
|
-
openSync,
|
|
19
|
-
readFileSync,
|
|
20
|
-
realpathSync,
|
|
21
|
-
renameSync,
|
|
22
|
-
writeFileSync,
|
|
23
|
-
} from "node:fs";
|
|
24
|
-
import { dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
25
|
-
import { fileURLToPath } from "node:url";
|
|
26
|
-
import { buildExternalRuntimeClosure } from "./external-runtime-builder.mjs";
|
|
27
|
-
import { canonicalHostWorkerDigest } from "./host-worker.mjs";
|
|
28
|
-
import {
|
|
29
|
-
EXTERNAL_HERMES_ENTRYPOINT,
|
|
30
|
-
EXTERNAL_MCP_ENTRYPOINT,
|
|
31
|
-
EXTERNAL_RUNTIME_CLOSURE_ROOT,
|
|
32
|
-
EXTERNAL_RUNTIME_MANIFEST_PATH,
|
|
33
|
-
} from "./runtime-helper.mjs";
|
|
34
|
-
import { RUNTIME_EGRESS_HOSTS } from "./runtime-boundary.mjs";
|
|
35
|
-
import { RUNTIME_EGRESS_PROXY_VERSION } from "./runtime-egress-proxy.mjs";
|
|
36
|
-
import { installAgentHostServices } from "./service-installer.mjs";
|
|
37
|
-
|
|
38
|
-
const PACKAGE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "../..");
|
|
39
|
-
const VERSION = "sellable-agent-host-bootstrap/v1";
|
|
40
|
-
const RECEIPT_VERSION = "sellable-agent-host-registration/v1";
|
|
41
|
-
const INSTALLER_PACKAGE = "@sellable/install@0.1.362";
|
|
42
|
-
const MCP_PACKAGE = "@sellable/mcp@0.1.620";
|
|
43
|
-
const HERMES_VERSION = "0.18.0";
|
|
44
|
-
const SHA256 = /^[a-f0-9]{64}$/;
|
|
45
|
-
const SHA256_16 = /^[a-f0-9]{16}$/;
|
|
46
|
-
const SHA512_INTEGRITY = /^sha512-[A-Za-z0-9+/]+={0,2}$/;
|
|
47
|
-
const SAFE_ID = /^[A-Za-z0-9_-]{1,128}$/;
|
|
48
|
-
const SAFE_TOOL = /^[a-z][a-z0-9_]{0,63}$/;
|
|
49
|
-
const SECRET = /xox[bap]-|sat_[A-Za-z0-9_-]+|asec\.v1\.|access[_-]?token|refresh[_-]?token|private[_-]?key/i;
|
|
50
|
-
|
|
51
|
-
const PATHS = Object.freeze({
|
|
52
|
-
providerSource: "/var/lib/sellable-agent-bootstrap/provider-auth",
|
|
53
|
-
xappSource: "/var/lib/sellable-agent-bootstrap/slack-app",
|
|
54
|
-
hostProbe: "/var/lib/sellable-agent-bootstrap/host-probe",
|
|
55
|
-
receipt: "/var/lib/sellable-agent-bootstrap/registration-receipt.json",
|
|
56
|
-
releaseIdentity: "/opt/sellable-agent/release.json",
|
|
57
|
-
workerRoot: "/var/lib/sellable-agent-worker",
|
|
58
|
-
workerRequests: "/var/lib/sellable-agent-worker/requests",
|
|
59
|
-
helperRequests: "/var/lib/sellable-agent-worker/helper-requests",
|
|
60
|
-
workerState: "/var/lib/sellable-agent-worker/state",
|
|
61
|
-
workerSlackSecrets: "/var/lib/sellable-agent-worker/runtime-secrets",
|
|
62
|
-
workerHostSecrets: "/var/lib/sellable-agent-worker/host-secrets",
|
|
63
|
-
xappRuntime: "/var/lib/sellable-agent-worker/host-secrets/slack-app",
|
|
64
|
-
signingRoot: "/var/lib/sellable-agent-worker/enrollment",
|
|
65
|
-
signingKey: "/var/lib/sellable-agent-worker/enrollment/private-key.pem",
|
|
66
|
-
runtimeRoot: "/var/lib/sellable-agent-runtime",
|
|
67
|
-
runtimeState: "/var/lib/sellable-agent-runtime/state",
|
|
68
|
-
runtimeMcpSecrets: "/var/lib/sellable-agent-runtime/mcp-secrets",
|
|
69
|
-
profiles: "/var/lib/sellable-agent-profiles",
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
const exactKeys = (value, keys) => Boolean(
|
|
73
|
-
value && typeof value === "object" && !Array.isArray(value) &&
|
|
74
|
-
Object.keys(value).sort().join("\0") === [...keys].sort().join("\0")
|
|
75
|
-
);
|
|
76
|
-
const sha256 = (value) => createHash("sha256").update(value).digest("hex");
|
|
77
|
-
|
|
78
|
-
function stable(value) {
|
|
79
|
-
if (Array.isArray(value)) return value.map(stable);
|
|
80
|
-
if (value && typeof value === "object") {
|
|
81
|
-
return Object.fromEntries(Object.keys(value).sort().map((key) => [key, stable(value[key])]));
|
|
82
|
-
}
|
|
83
|
-
return value;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function targetPath(targetRoot, absolutePath) {
|
|
87
|
-
const root = realpathSync(resolve(targetRoot));
|
|
88
|
-
const value = root === "/" ? resolve(absolutePath) : join(root, absolutePath.replace(/^\/+/, ""));
|
|
89
|
-
const rel = relative(root, value);
|
|
90
|
-
if (!isAbsolute(absolutePath) || rel.startsWith("..") || isAbsolute(rel)) {
|
|
91
|
-
throw new Error("host bootstrap target rejected");
|
|
92
|
-
}
|
|
93
|
-
return value;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function atomicFile(pathValue, bytes, mode, { uid, gid, disposableFixture }) {
|
|
97
|
-
const temp = join(dirname(pathValue), `.${randomUUID()}.tmp`);
|
|
98
|
-
writeFileSync(temp, bytes, { flag: "wx", mode });
|
|
99
|
-
chmodSync(temp, mode);
|
|
100
|
-
if (!disposableFixture) chownSync(temp, uid, gid);
|
|
101
|
-
const descriptor = openSync(temp, "r");
|
|
102
|
-
try { fsyncSync(descriptor); } finally { closeSync(descriptor); }
|
|
103
|
-
renameSync(temp, pathValue);
|
|
104
|
-
const parent = openSync(dirname(pathValue), "r");
|
|
105
|
-
try { fsyncSync(parent); } finally { closeSync(parent); }
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
function ensureDirectory(targetRoot, absolutePath, mode, uid, gid, disposableFixture) {
|
|
109
|
-
const value = targetPath(targetRoot, absolutePath);
|
|
110
|
-
if (!existsSync(value)) mkdirSync(value, { recursive: true, mode });
|
|
111
|
-
const link = lstatSync(value);
|
|
112
|
-
if (link.isSymbolicLink() || !link.isDirectory()) throw new Error("host bootstrap directory rejected");
|
|
113
|
-
chmodSync(value, mode);
|
|
114
|
-
if (!disposableFixture) chownSync(value, uid, gid);
|
|
115
|
-
const observed = lstatSync(value);
|
|
116
|
-
if (
|
|
117
|
-
(observed.mode & 0o777) !== mode ||
|
|
118
|
-
(!disposableFixture && (observed.uid !== uid || observed.gid !== gid))
|
|
119
|
-
) throw new Error("host bootstrap directory readback rejected");
|
|
120
|
-
return value;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
function readPrivateSource(targetRoot, absolutePath, disposableFixture) {
|
|
124
|
-
const value = targetPath(targetRoot, absolutePath);
|
|
125
|
-
const parent = lstatSync(dirname(value));
|
|
126
|
-
const link = lstatSync(value);
|
|
127
|
-
const expectedUid = disposableFixture ? (process.getuid?.() ?? link.uid) : 0;
|
|
128
|
-
const expectedGid = disposableFixture ? (process.getgid?.() ?? link.gid) : 0;
|
|
129
|
-
if (
|
|
130
|
-
parent.isSymbolicLink() || !parent.isDirectory() || (parent.mode & 0o777) !== 0o700 ||
|
|
131
|
-
parent.uid !== expectedUid || parent.gid !== expectedGid ||
|
|
132
|
-
link.isSymbolicLink() || !link.isFile() || (link.mode & 0o777) !== 0o600 ||
|
|
133
|
-
link.uid !== expectedUid || link.gid !== expectedGid || realpathSync(dirname(value)) !== dirname(value)
|
|
134
|
-
) throw new Error("host bootstrap private source rejected");
|
|
135
|
-
const descriptor = openSync(value, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
|
|
136
|
-
try {
|
|
137
|
-
const opened = fstatSync(descriptor);
|
|
138
|
-
if (!opened.isFile() || opened.dev !== link.dev || opened.ino !== link.ino) {
|
|
139
|
-
throw new Error("host bootstrap private source changed");
|
|
140
|
-
}
|
|
141
|
-
return readFileSync(descriptor, "utf8").trim();
|
|
142
|
-
} finally {
|
|
143
|
-
closeSync(descriptor);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
function readReleaseIdentity(targetRoot, expected, disposableFixture) {
|
|
148
|
-
const value = targetPath(targetRoot, PATHS.releaseIdentity);
|
|
149
|
-
const link = lstatSync(value);
|
|
150
|
-
const expectedUid = disposableFixture ? (process.getuid?.() ?? link.uid) : 0;
|
|
151
|
-
const expectedGid = disposableFixture ? (process.getgid?.() ?? link.gid) : 0;
|
|
152
|
-
if (
|
|
153
|
-
link.isSymbolicLink() || !link.isFile() || (link.mode & 0o777) !== 0o444 ||
|
|
154
|
-
link.uid !== expectedUid || link.gid !== expectedGid
|
|
155
|
-
) throw new Error("host bootstrap image release rejected");
|
|
156
|
-
const observed = JSON.parse(readFileSync(value, "utf8"));
|
|
157
|
-
if (!exactKeys(observed, ["installerPackage", "installerIntegrity", "mcpPackage", "mcpIntegrity"]) ||
|
|
158
|
-
JSON.stringify(stable(observed)) !== JSON.stringify(stable(expected))) {
|
|
159
|
-
throw new Error("host bootstrap image release mismatch");
|
|
160
|
-
}
|
|
161
|
-
return observed;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
function validateConfig(config) {
|
|
165
|
-
if (!exactKeys(config, [
|
|
166
|
-
"version", "target", "host", "enrollment", "release", "controlPlaneUrl",
|
|
167
|
-
"runtimeContract", "identities",
|
|
168
|
-
]) || config.version !== VERSION || SECRET.test(JSON.stringify(config))) {
|
|
169
|
-
throw new Error("host bootstrap config rejected");
|
|
170
|
-
}
|
|
171
|
-
if (
|
|
172
|
-
!exactKeys(config.target, ["vpsId", "hostname", "containerName"]) ||
|
|
173
|
-
Object.values(config.target).some((value) => !SAFE_ID.test(value ?? "")) ||
|
|
174
|
-
!exactKeys(config.host, ["id", "stableKey", "compatibilityHash", "capacity"]) ||
|
|
175
|
-
!SAFE_ID.test(config.host.id ?? "") || !SAFE_ID.test(config.host.stableKey ?? "") ||
|
|
176
|
-
!SHA256.test(config.host.compatibilityHash ?? "") ||
|
|
177
|
-
!Number.isSafeInteger(config.host.capacity) || config.host.capacity < 1 || config.host.capacity > 100 ||
|
|
178
|
-
!exactKeys(config.enrollment, ["id", "workerId", "keyGeneration", "expiresAt"]) ||
|
|
179
|
-
!SAFE_ID.test(config.enrollment.id ?? "") || !SAFE_ID.test(config.enrollment.workerId ?? "") ||
|
|
180
|
-
!Number.isSafeInteger(config.enrollment.keyGeneration) || config.enrollment.keyGeneration < 1 ||
|
|
181
|
-
!Number.isFinite(Date.parse(config.enrollment.expiresAt ?? "")) ||
|
|
182
|
-
Date.parse(config.enrollment.expiresAt) <= Date.now() ||
|
|
183
|
-
!exactKeys(config.release, ["installerPackage", "installerIntegrity", "mcpPackage", "mcpIntegrity"]) ||
|
|
184
|
-
config.release.installerPackage !== INSTALLER_PACKAGE ||
|
|
185
|
-
config.release.mcpPackage !== MCP_PACKAGE ||
|
|
186
|
-
!SHA512_INTEGRITY.test(config.release.installerIntegrity ?? "") ||
|
|
187
|
-
!SHA512_INTEGRITY.test(config.release.mcpIntegrity ?? "") ||
|
|
188
|
-
!/^https:\/\/[A-Za-z0-9.-]+(?::[0-9]+)?$/.test(config.controlPlaneUrl ?? "")
|
|
189
|
-
) throw new Error("host bootstrap identity rejected");
|
|
190
|
-
const runtime = config.runtimeContract;
|
|
191
|
-
if (
|
|
192
|
-
!exactKeys(runtime, [
|
|
193
|
-
"profileId", "providerReference", "providerFingerprint", "xappReference",
|
|
194
|
-
"xappFingerprint", "policyHash", "toolInclude",
|
|
195
|
-
]) || !SAFE_ID.test(runtime.profileId ?? "") ||
|
|
196
|
-
typeof runtime.providerReference !== "string" || !runtime.providerReference || runtime.providerReference.length > 256 ||
|
|
197
|
-
!SHA256.test(runtime.providerFingerprint ?? "") ||
|
|
198
|
-
typeof runtime.xappReference !== "string" || !runtime.xappReference || runtime.xappReference.length > 256 ||
|
|
199
|
-
!SHA256_16.test(runtime.xappFingerprint ?? "") ||
|
|
200
|
-
!SHA256.test(runtime.policyHash ?? "") || !Array.isArray(runtime.toolInclude) ||
|
|
201
|
-
runtime.toolInclude.length < 1 || runtime.toolInclude.length > 64 ||
|
|
202
|
-
runtime.toolInclude.some((tool) => !SAFE_TOOL.test(tool)) ||
|
|
203
|
-
JSON.stringify(runtime.toolInclude) !== JSON.stringify([...new Set(runtime.toolInclude)].sort())
|
|
204
|
-
) throw new Error("host bootstrap runtime contract rejected");
|
|
205
|
-
const identities = config.identities;
|
|
206
|
-
if (
|
|
207
|
-
!exactKeys(identities, ["workerUid", "workerGid", "proxyUid", "proxyGid", "runtimeUid", "runtimeGid", "siblingUid", "siblingGid", "adminUid", "adminGid"]) ||
|
|
208
|
-
Object.values(identities).some((value) => !Number.isSafeInteger(value) || value < 1) ||
|
|
209
|
-
new Set([identities.workerUid, identities.proxyUid, identities.runtimeUid, identities.siblingUid, identities.adminUid]).size !== 5
|
|
210
|
-
) throw new Error("host bootstrap runtime identities rejected");
|
|
211
|
-
return config;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
function signingIdentity(targetRoot, config, disposableFixture) {
|
|
215
|
-
const keyPath = targetPath(targetRoot, PATHS.signingKey);
|
|
216
|
-
if (!existsSync(keyPath)) {
|
|
217
|
-
const { privateKey } = generateKeyPairSync("ed25519");
|
|
218
|
-
atomicFile(
|
|
219
|
-
keyPath,
|
|
220
|
-
privateKey.export({ type: "pkcs8", format: "pem" }).toString(),
|
|
221
|
-
0o600,
|
|
222
|
-
{ uid: config.identities.workerUid, gid: config.identities.workerGid, disposableFixture },
|
|
223
|
-
);
|
|
224
|
-
}
|
|
225
|
-
const link = lstatSync(keyPath);
|
|
226
|
-
if (
|
|
227
|
-
link.isSymbolicLink() || !link.isFile() || (link.mode & 0o777) !== 0o600 ||
|
|
228
|
-
(!disposableFixture && (link.uid !== config.identities.workerUid || link.gid !== config.identities.workerGid))
|
|
229
|
-
) throw new Error("host bootstrap signing key rejected");
|
|
230
|
-
const privateKey = createPrivateKey(readFileSync(keyPath, "utf8"));
|
|
231
|
-
if (privateKey.asymmetricKeyType !== "ed25519") throw new Error("host bootstrap signing key type rejected");
|
|
232
|
-
const publicKeyPem = createPublicKey(privateKey).export({ type: "spki", format: "pem" }).toString();
|
|
233
|
-
const publicKeyHash = sha256(publicKeyPem);
|
|
234
|
-
const enrollmentFingerprint = canonicalHostWorkerDigest({
|
|
235
|
-
audience: "sellable-agent-worker/v1",
|
|
236
|
-
enrollmentId: config.enrollment.id,
|
|
237
|
-
hostId: config.host.id,
|
|
238
|
-
keyGeneration: config.enrollment.keyGeneration,
|
|
239
|
-
publicKeyHash,
|
|
240
|
-
workerId: config.enrollment.workerId,
|
|
241
|
-
});
|
|
242
|
-
return { publicKeyHash, enrollmentFingerprint };
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
function prepareLayout(targetRoot, config, disposableFixture) {
|
|
246
|
-
const ids = config.identities;
|
|
247
|
-
for (const pathValue of [
|
|
248
|
-
PATHS.workerRoot, PATHS.workerRequests, PATHS.helperRequests, PATHS.workerState,
|
|
249
|
-
PATHS.workerSlackSecrets, PATHS.workerHostSecrets, PATHS.signingRoot,
|
|
250
|
-
]) ensureDirectory(targetRoot, pathValue, 0o700, ids.workerUid, ids.workerGid, disposableFixture);
|
|
251
|
-
for (const pathValue of [PATHS.runtimeRoot, PATHS.runtimeState, PATHS.runtimeMcpSecrets]) {
|
|
252
|
-
ensureDirectory(targetRoot, pathValue, 0o700, ids.runtimeUid, ids.runtimeGid, disposableFixture);
|
|
253
|
-
}
|
|
254
|
-
ensureDirectory(targetRoot, PATHS.profiles, 0o711, ids.workerUid, ids.workerGid, disposableFixture);
|
|
255
|
-
const probePath = targetPath(targetRoot, PATHS.hostProbe);
|
|
256
|
-
if (!existsSync(probePath)) {
|
|
257
|
-
atomicFile(probePath, "host-only\n", 0o600, { uid: 0, gid: 0, disposableFixture });
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
function installHostAppSecret(targetRoot, config, disposableFixture) {
|
|
262
|
-
const source = readPrivateSource(targetRoot, PATHS.xappSource, disposableFixture);
|
|
263
|
-
if (!source || source.length > 4096 || sha256(source).slice(0, 16) !== config.runtimeContract.xappFingerprint) {
|
|
264
|
-
throw new Error("host bootstrap Slack app source rejected");
|
|
265
|
-
}
|
|
266
|
-
const target = targetPath(targetRoot, PATHS.xappRuntime);
|
|
267
|
-
atomicFile(target, `${source}\n`, 0o600, {
|
|
268
|
-
uid: config.identities.workerUid,
|
|
269
|
-
gid: config.identities.workerGid,
|
|
270
|
-
disposableFixture,
|
|
271
|
-
});
|
|
272
|
-
return target;
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
function configs(config, signing, ownerUid, ownerGid) {
|
|
276
|
-
const ids = config.identities;
|
|
277
|
-
const workerConfig = {
|
|
278
|
-
enrollmentId: config.enrollment.id,
|
|
279
|
-
hostId: config.host.id,
|
|
280
|
-
workerId: config.enrollment.workerId,
|
|
281
|
-
signerGeneration: config.enrollment.keyGeneration,
|
|
282
|
-
enrollmentFingerprint: signing.enrollmentFingerprint,
|
|
283
|
-
publicKeyHash: signing.publicKeyHash,
|
|
284
|
-
privateKeyPath: PATHS.signingKey,
|
|
285
|
-
controlPlaneUrl: config.controlPlaneUrl,
|
|
286
|
-
requestRoot: PATHS.workerRequests,
|
|
287
|
-
helperRequestRoot: PATHS.helperRequests,
|
|
288
|
-
stateRoot: PATHS.workerState,
|
|
289
|
-
profilesRoot: PATHS.profiles,
|
|
290
|
-
runtimeSecretsRoot: PATHS.workerSlackSecrets,
|
|
291
|
-
runtimeMcpSecretsRoot: PATHS.runtimeMcpSecrets,
|
|
292
|
-
workerUid: ids.workerUid,
|
|
293
|
-
workerGid: ids.workerGid,
|
|
294
|
-
runtimeUid: ids.runtimeUid,
|
|
295
|
-
runtimeGid: ids.runtimeGid,
|
|
296
|
-
leaseSeconds: 30,
|
|
297
|
-
poll: { minDelayMs: 1_000, maxDelayMs: 30_000, jitterRatio: 0.2 },
|
|
298
|
-
profileRuntimeContract: {
|
|
299
|
-
hermesCli: "hermes",
|
|
300
|
-
hermesVersion: HERMES_VERSION,
|
|
301
|
-
installerPackage: INSTALLER_PACKAGE,
|
|
302
|
-
mcpPackage: MCP_PACKAGE,
|
|
303
|
-
providerReference: config.runtimeContract.providerReference,
|
|
304
|
-
providerFingerprint: config.runtimeContract.providerFingerprint,
|
|
305
|
-
xappReference: config.runtimeContract.xappReference,
|
|
306
|
-
xappFingerprint: config.runtimeContract.xappFingerprint,
|
|
307
|
-
xappSecretFile: PATHS.xappRuntime,
|
|
308
|
-
policies: {
|
|
309
|
-
[config.runtimeContract.policyHash]: { toolInclude: config.runtimeContract.toolInclude },
|
|
310
|
-
},
|
|
311
|
-
},
|
|
312
|
-
};
|
|
313
|
-
const helperProfile = {
|
|
314
|
-
workerUid: ids.workerUid,
|
|
315
|
-
workerGid: ids.workerGid,
|
|
316
|
-
runtimeUid: ids.runtimeUid,
|
|
317
|
-
runtimeGid: ids.runtimeGid,
|
|
318
|
-
siblingUid: ids.siblingUid,
|
|
319
|
-
siblingGid: ids.siblingGid,
|
|
320
|
-
adminUid: ids.adminUid,
|
|
321
|
-
adminGid: ids.adminGid,
|
|
322
|
-
proxyUid: ids.proxyUid,
|
|
323
|
-
proxyGid: ids.proxyGid,
|
|
324
|
-
profilesRoot: PATHS.profiles,
|
|
325
|
-
workerRuntimeSecretRoot: PATHS.workerSlackSecrets,
|
|
326
|
-
mcpCredentialRoot: PATHS.runtimeMcpSecrets,
|
|
327
|
-
runtimeStateRoot: PATHS.runtimeState,
|
|
328
|
-
proxyExecutablePath: "/usr/local/lib/sellable-agent/package/bin/sellable-agent-egress-proxy.mjs",
|
|
329
|
-
installedPackageManifestPath: "/usr/local/lib/sellable-agent/package/manifest.json",
|
|
330
|
-
externalRuntimeManifestPath: EXTERNAL_RUNTIME_MANIFEST_PATH,
|
|
331
|
-
externalRuntimeClosureRoot: EXTERNAL_RUNTIME_CLOSURE_ROOT,
|
|
332
|
-
externalRuntimeTrustRoot: "/usr/local/lib/sellable-agent",
|
|
333
|
-
externalRuntimePathRoot: "/",
|
|
334
|
-
externalRuntimeOwnerUid: ownerUid,
|
|
335
|
-
externalRuntimeOwnerGid: ownerGid,
|
|
336
|
-
mcpCommand: EXTERNAL_MCP_ENTRYPOINT,
|
|
337
|
-
hermesExecutablePath: EXTERNAL_HERMES_ENTRYPOINT,
|
|
338
|
-
hermesSourceRoot: `${EXTERNAL_RUNTIME_CLOSURE_ROOT}/hermes`,
|
|
339
|
-
proxyConfigPath: "/etc/sellable-agent/egress-proxy.json",
|
|
340
|
-
runtimeBoundaryConfigPath: "/etc/sellable-agent/runtime-boundary.json",
|
|
341
|
-
providerCredentialSourcePath: PATHS.providerSource,
|
|
342
|
-
providerCredentialFingerprint: config.runtimeContract.providerFingerprint,
|
|
343
|
-
runtimeServicePath: "/etc/services.d/sellable-agent-runtime",
|
|
344
|
-
runtimeServiceEnvRoot: "/etc/services.d/sellable-agent-runtime/env",
|
|
345
|
-
runtimeProbePath: "/usr/local/bin/sellable-agent-runtime-probe",
|
|
346
|
-
proxyPidPath: "/etc/services.d/sellable-agent-egress-proxy/supervise/pid",
|
|
347
|
-
proxyPort: 19081,
|
|
348
|
-
procRoot: "/proc",
|
|
349
|
-
bootIdPath: "/proc/sys/kernel/random/boot_id",
|
|
350
|
-
bubblewrapPath: "/usr/bin/bwrap",
|
|
351
|
-
setprivPath: "/usr/bin/setpriv",
|
|
352
|
-
nftPath: "/usr/sbin/nft",
|
|
353
|
-
cgroupRoot: "/sys/fs/cgroup/sellable-agent",
|
|
354
|
-
hostProbePath: PATHS.hostProbe,
|
|
355
|
-
dockerSocketPath: "/var/run/docker.sock",
|
|
356
|
-
};
|
|
357
|
-
const helperConfig = {
|
|
358
|
-
helperRequestRoot: PATHS.helperRequests,
|
|
359
|
-
workerRequestRoot: PATHS.workerRequests,
|
|
360
|
-
s6SvcPath: "/command/s6-svc",
|
|
361
|
-
profiles: { [config.runtimeContract.profileId]: helperProfile },
|
|
362
|
-
};
|
|
363
|
-
const egressProxyConfig = {
|
|
364
|
-
version: RUNTIME_EGRESS_PROXY_VERSION,
|
|
365
|
-
listenHost: "127.0.0.1",
|
|
366
|
-
listenPort: 19081,
|
|
367
|
-
uid: ids.proxyUid,
|
|
368
|
-
gid: ids.proxyGid,
|
|
369
|
-
allowedHosts: [...RUNTIME_EGRESS_HOSTS],
|
|
370
|
-
};
|
|
371
|
-
return { workerConfig, helperConfig, egressProxyConfig };
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
export function bootstrapAgentHost(input) {
|
|
375
|
-
const config = validateConfig(input.config);
|
|
376
|
-
const targetRoot = resolve(input.targetRoot ?? "/");
|
|
377
|
-
const disposableFixture = input.disposableFixture === true;
|
|
378
|
-
if (!isAbsolute(targetRoot) || !existsSync(targetRoot) || (targetRoot !== "/" && !disposableFixture)) {
|
|
379
|
-
throw new Error("host bootstrap root rejected");
|
|
380
|
-
}
|
|
381
|
-
if (targetRoot === "/" && (process.getuid?.() !== 0 || process.getgid?.() !== 0)) {
|
|
382
|
-
throw new Error("host bootstrap requires root");
|
|
383
|
-
}
|
|
384
|
-
readReleaseIdentity(targetRoot, config.release, disposableFixture);
|
|
385
|
-
const provider = readPrivateSource(targetRoot, PATHS.providerSource, disposableFixture);
|
|
386
|
-
if (!provider || provider.length > 1024 * 1024 || sha256(provider) !== config.runtimeContract.providerFingerprint) {
|
|
387
|
-
throw new Error("host bootstrap provider source rejected");
|
|
388
|
-
}
|
|
389
|
-
const providerJson = JSON.parse(provider);
|
|
390
|
-
if (!providerJson || typeof providerJson !== "object" || Array.isArray(providerJson)) {
|
|
391
|
-
throw new Error("host bootstrap provider JSON rejected");
|
|
392
|
-
}
|
|
393
|
-
prepareLayout(targetRoot, config, disposableFixture);
|
|
394
|
-
installHostAppSecret(targetRoot, config, disposableFixture);
|
|
395
|
-
const signing = signingIdentity(targetRoot, config, disposableFixture);
|
|
396
|
-
const sourceRoots = disposableFixture ? input.sourceRoots : {
|
|
397
|
-
mcpPackageRoot: "/opt/sellable-agent/mcp-root/node_modules/@sellable/mcp",
|
|
398
|
-
mcpNodeModulesRoot: "/opt/sellable-agent/mcp-root/node_modules",
|
|
399
|
-
hermesSourceRoot: "/opt/hermes",
|
|
400
|
-
};
|
|
401
|
-
const externalRuntime = buildExternalRuntimeClosure({
|
|
402
|
-
pathRoot: targetRoot,
|
|
403
|
-
ownerUid: disposableFixture ? (process.getuid?.() ?? 0) : 0,
|
|
404
|
-
ownerGid: disposableFixture ? (process.getgid?.() ?? 0) : 0,
|
|
405
|
-
disposableFixture,
|
|
406
|
-
...(sourceRoots ?? {}),
|
|
407
|
-
...(disposableFixture && input.hermesBridgeContract ? { hermesBridgeContract: input.hermesBridgeContract } : {}),
|
|
408
|
-
});
|
|
409
|
-
const ownerUid = disposableFixture ? (process.getuid?.() ?? 0) : 0;
|
|
410
|
-
const ownerGid = disposableFixture ? (process.getgid?.() ?? 0) : 0;
|
|
411
|
-
const generated = configs(config, signing, ownerUid, ownerGid);
|
|
412
|
-
const installed = installAgentHostServices({
|
|
413
|
-
targetRoot,
|
|
414
|
-
packageRoot: input.packageRoot ?? PACKAGE_ROOT,
|
|
415
|
-
ownerUid,
|
|
416
|
-
ownerGid,
|
|
417
|
-
disposableFixture,
|
|
418
|
-
...(disposableFixture && input.accountProvisioner ? { accountProvisioner: input.accountProvisioner } : {}),
|
|
419
|
-
...generated,
|
|
420
|
-
});
|
|
421
|
-
if (!installed.ok) throw new Error(installed.code);
|
|
422
|
-
const configurationDigest = sha256(JSON.stringify(stable(generated)));
|
|
423
|
-
const receipt = {
|
|
424
|
-
version: RECEIPT_VERSION,
|
|
425
|
-
producer: "sellable-agent-host-bootstrap",
|
|
426
|
-
target: config.target,
|
|
427
|
-
host: config.host,
|
|
428
|
-
enrollment: {
|
|
429
|
-
id: config.enrollment.id,
|
|
430
|
-
workerId: config.enrollment.workerId,
|
|
431
|
-
audience: "sellable-agent-worker/v1",
|
|
432
|
-
keyGeneration: config.enrollment.keyGeneration,
|
|
433
|
-
publicKeyHash: signing.publicKeyHash,
|
|
434
|
-
enrollmentFingerprint: signing.enrollmentFingerprint,
|
|
435
|
-
capabilities: ["claim", "heartbeat", "secret-consume", "complete"],
|
|
436
|
-
expiresAt: new Date(config.enrollment.expiresAt).toISOString(),
|
|
437
|
-
},
|
|
438
|
-
release: {
|
|
439
|
-
...config.release,
|
|
440
|
-
serviceManifestDigest: installed.manifestDigest,
|
|
441
|
-
externalRuntimeClosureDigest: externalRuntime.closureDigest,
|
|
442
|
-
configurationDigest,
|
|
443
|
-
},
|
|
444
|
-
secretScan: { unexpectedMatches: 0 },
|
|
445
|
-
observedAt: (input.now ?? new Date()).toISOString(),
|
|
446
|
-
};
|
|
447
|
-
if (SECRET.test(JSON.stringify(receipt))) throw new Error("host bootstrap receipt rejected");
|
|
448
|
-
atomicFile(
|
|
449
|
-
targetPath(targetRoot, PATHS.receipt),
|
|
450
|
-
`${JSON.stringify(receipt, null, 2)}\n`,
|
|
451
|
-
0o600,
|
|
452
|
-
{ uid: 0, gid: 0, disposableFixture },
|
|
453
|
-
);
|
|
454
|
-
return { ok: true, receipt, workerConfig: generated.workerConfig, helperConfig: generated.helperConfig };
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
export const SELLABLE_AGENT_HOST_BOOTSTRAP_VERSION = VERSION;
|
|
458
|
-
export const SELLABLE_AGENT_HOST_BOOTSTRAP_PATHS = PATHS;
|