@sellable/install 0.1.358 → 0.1.359-phase111.20260720052618
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 +0 -2
- package/package.json +2 -11
- package/skill-templates/refill-sends-evergreen.md +5 -0
- package/skill-templates/refill-sends-v2.md +23 -1
- package/skill-templates/refill-sends.md +14 -1
- package/bin/sellable-agent-egress-proxy.mjs +0 -31
- package/bin/sellable-agent-host-bootstrap.mjs +0 -26
- package/bin/sellable-agent-host-worker.mjs +0 -9
- package/bin/sellable-agent-runtime-helper.mjs +0 -37
- package/bin/sellable-agent-runtime-launcher.mjs +0 -151
- package/bin/sellable-agent-runtime-probe.mjs +0 -163
- package/bin/sellable-agent-sandbox-init.mjs +0 -93
- package/container/Dockerfile +0 -37
- package/container/README.md +0 -15
- package/container/compose.yaml +0 -22
- package/container/entrypoint.sh +0 -13
- package/lib/sellable-agent/containment-contract.mjs +0 -472
- package/lib/sellable-agent/external-runtime-builder.mjs +0 -272
- package/lib/sellable-agent/hermes-bridge.mjs +0 -497
- package/lib/sellable-agent/host-bootstrap.mjs +0 -458
- package/lib/sellable-agent/host-worker.mjs +0 -2067
- package/lib/sellable-agent/profile-materializer.mjs +0 -1410
- package/lib/sellable-agent/provisioning-adapter.mjs +0 -992
- package/lib/sellable-agent/runtime-boundary.mjs +0 -635
- package/lib/sellable-agent/runtime-egress-proxy.mjs +0 -241
- package/lib/sellable-agent/runtime-helper.mjs +0 -1428
- package/lib/sellable-agent/runtime-reconciler.mjs +0 -683
- package/lib/sellable-agent/service-installer.mjs +0 -441
- package/services/s6/sellable-agent-egress-proxy/run +0 -15
- package/services/s6/sellable-agent-host-worker/run +0 -4
- package/services/s6/sellable-agent-runtime/run +0 -19
|
@@ -1,441 +0,0 @@
|
|
|
1
|
-
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
-
import { spawnSync } from "node:child_process";
|
|
3
|
-
import {
|
|
4
|
-
chmodSync, chownSync, closeSync, existsSync, fsyncSync, lstatSync, mkdirSync,
|
|
5
|
-
openSync, readFileSync, realpathSync, renameSync, writeFileSync,
|
|
6
|
-
} from "node:fs";
|
|
7
|
-
import { dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
8
|
-
import { fileURLToPath } from "node:url";
|
|
9
|
-
import { validateRuntimeEgressProxyConfig } from "./runtime-egress-proxy.mjs";
|
|
10
|
-
import {
|
|
11
|
-
EXTERNAL_HERMES_ENTRYPOINT,
|
|
12
|
-
EXTERNAL_MCP_ENTRYPOINT,
|
|
13
|
-
EXTERNAL_RUNTIME_CLOSURE_ROOT,
|
|
14
|
-
EXTERNAL_RUNTIME_MANIFEST_PATH,
|
|
15
|
-
observeExternalRuntimeClosure,
|
|
16
|
-
} from "./runtime-helper.mjs";
|
|
17
|
-
|
|
18
|
-
const PACKAGE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "../..");
|
|
19
|
-
const IMMUTABLE_PACKAGE_ROOT = "/usr/local/lib/sellable-agent/package";
|
|
20
|
-
const IMMUTABLE_MANIFEST_PATH = `${IMMUTABLE_PACKAGE_ROOT}/manifest.json`;
|
|
21
|
-
const IMMUTABLE_MANIFEST_VERSION = "sellable-agent-installed-closure/v1";
|
|
22
|
-
const AGENT_BIN_FILES = Object.freeze([
|
|
23
|
-
"sellable-agent-egress-proxy.mjs",
|
|
24
|
-
"sellable-agent-host-worker.mjs",
|
|
25
|
-
"sellable-agent-runtime-helper.mjs",
|
|
26
|
-
"sellable-agent-runtime-launcher.mjs",
|
|
27
|
-
"sellable-agent-runtime-probe.mjs",
|
|
28
|
-
"sellable-agent-sandbox-init.mjs",
|
|
29
|
-
]);
|
|
30
|
-
const AGENT_LIB_FILES = Object.freeze([
|
|
31
|
-
"containment-contract.mjs",
|
|
32
|
-
"hermes-bridge.mjs",
|
|
33
|
-
"host-worker.mjs",
|
|
34
|
-
"profile-materializer.mjs",
|
|
35
|
-
"provisioning-adapter.mjs",
|
|
36
|
-
"runtime-boundary.mjs",
|
|
37
|
-
"runtime-egress-proxy.mjs",
|
|
38
|
-
"runtime-helper.mjs",
|
|
39
|
-
"runtime-reconciler.mjs",
|
|
40
|
-
"service-installer.mjs",
|
|
41
|
-
]);
|
|
42
|
-
const SAFE_ID = /^[A-Za-z0-9_-]{1,128}$/;
|
|
43
|
-
const SHA256 = /^[a-f0-9]{64}$/;
|
|
44
|
-
const SECRET = /xox[bap]-|sat_[A-Za-z0-9_-]+|asec\.v1\.|access[_-]?token|refresh[_-]?token/i;
|
|
45
|
-
|
|
46
|
-
function sha256(value) {
|
|
47
|
-
return createHash("sha256").update(value).digest("hex");
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function inside(root, target) {
|
|
51
|
-
const value = relative(root, target);
|
|
52
|
-
return value === "" || (!value.startsWith("..") && !isAbsolute(value));
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function ensureTrustedParents(root, pathValue, uid, gid) {
|
|
56
|
-
const parent = dirname(pathValue);
|
|
57
|
-
if (!inside(root, parent)) throw new Error("service parent confinement failed");
|
|
58
|
-
const relativeParent = relative(root, parent);
|
|
59
|
-
let current = root;
|
|
60
|
-
for (const component of relativeParent.split(/[/\\]+/).filter(Boolean)) {
|
|
61
|
-
current = join(current, component);
|
|
62
|
-
if (!existsSync(current)) mkdirSync(current, { mode: 0o755 });
|
|
63
|
-
const link = lstatSync(current);
|
|
64
|
-
if (
|
|
65
|
-
link.isSymbolicLink() || !link.isDirectory() || link.uid !== uid || link.gid !== gid ||
|
|
66
|
-
(link.mode & 0o022) !== 0 || realpathSync(current) !== current
|
|
67
|
-
) throw new Error("service parent rejected");
|
|
68
|
-
}
|
|
69
|
-
if (existsSync(pathValue) && lstatSync(pathValue).isSymbolicLink()) {
|
|
70
|
-
throw new Error("service target symlink rejected");
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function atomicOwnedFile(root, pathValue, bytes, mode, uid, gid) {
|
|
75
|
-
ensureTrustedParents(root, pathValue, uid, gid);
|
|
76
|
-
const temp = join(dirname(pathValue), `.${randomUUID()}.tmp`);
|
|
77
|
-
writeFileSync(temp, bytes, { flag: "wx", mode });
|
|
78
|
-
chmodSync(temp, mode);
|
|
79
|
-
if (lstatSync(temp).uid !== uid || lstatSync(temp).gid !== gid) chownSync(temp, uid, gid);
|
|
80
|
-
const descriptor = openSync(temp, "r");
|
|
81
|
-
try { fsyncSync(descriptor); } finally { closeSync(descriptor); }
|
|
82
|
-
renameSync(temp, pathValue);
|
|
83
|
-
const parent = openSync(dirname(pathValue), "r");
|
|
84
|
-
try { fsyncSync(parent); } finally { closeSync(parent); }
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
function accountDatabaseState(targetRoot, expected, ownerUid, ownerGid) {
|
|
88
|
-
const passwdPath = target(targetRoot, "/etc/passwd");
|
|
89
|
-
const groupPath = target(targetRoot, "/etc/group");
|
|
90
|
-
const passwdInfo = lstatSync(passwdPath);
|
|
91
|
-
const groupInfo = lstatSync(groupPath);
|
|
92
|
-
if (
|
|
93
|
-
passwdInfo.isSymbolicLink() || !passwdInfo.isFile() || (passwdInfo.mode & 0o022) !== 0 ||
|
|
94
|
-
passwdInfo.uid !== ownerUid || passwdInfo.gid !== ownerGid ||
|
|
95
|
-
groupInfo.isSymbolicLink() || !groupInfo.isFile() || (groupInfo.mode & 0o022) !== 0 ||
|
|
96
|
-
groupInfo.uid !== ownerUid || groupInfo.gid !== ownerGid
|
|
97
|
-
) throw new Error("service account database rejected");
|
|
98
|
-
const passwd = readFileSync(passwdPath, "utf8").split(/\r?\n/).filter(Boolean).map((line) => line.split(":"));
|
|
99
|
-
const groups = readFileSync(groupPath, "utf8").split(/\r?\n/).filter(Boolean).map((line) => line.split(":"));
|
|
100
|
-
const state = expected.map((account) => {
|
|
101
|
-
const users = passwd.filter((fields) => fields[0] === account.name);
|
|
102
|
-
const groupRows = groups.filter((fields) => fields[0] === account.name);
|
|
103
|
-
const uidRows = passwd.filter((fields) => Number(fields[2]) === account.uid);
|
|
104
|
-
const gidRows = groups.filter((fields) => Number(fields[2]) === account.gid);
|
|
105
|
-
const userPresent = users.length === 1 && users[0].length >= 7 &&
|
|
106
|
-
Number(users[0][2]) === account.uid && Number(users[0][3]) === account.gid &&
|
|
107
|
-
users[0][5] === account.home && users[0][6] === "/usr/sbin/nologin" &&
|
|
108
|
-
uidRows.length === 1 && uidRows[0] === users[0];
|
|
109
|
-
const groupPresent = groupRows.length === 1 && groupRows[0].length >= 4 &&
|
|
110
|
-
Number(groupRows[0][2]) === account.gid && gidRows.length === 1 && gidRows[0] === groupRows[0];
|
|
111
|
-
if ((!userPresent && (users.length > 0 || uidRows.length > 0)) ||
|
|
112
|
-
(!groupPresent && (groupRows.length > 0 || gidRows.length > 0))) {
|
|
113
|
-
throw new Error("service account identity collision");
|
|
114
|
-
}
|
|
115
|
-
return { ...account, userPresent, groupPresent };
|
|
116
|
-
});
|
|
117
|
-
return state;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
function readAccountDatabase(targetRoot, expected, ownerUid, ownerGid) {
|
|
121
|
-
const state = accountDatabaseState(targetRoot, expected, ownerUid, ownerGid);
|
|
122
|
-
if (state.some((account) => !account.userPresent || !account.groupPresent)) {
|
|
123
|
-
throw new Error("service account identity rejected");
|
|
124
|
-
}
|
|
125
|
-
return state.map(({ name, uid, gid }) => ({ name, uid, gid }));
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
function runAccountCommand(command, args) {
|
|
129
|
-
const result = spawnSync(command, args, {
|
|
130
|
-
encoding: "utf8",
|
|
131
|
-
env: { PATH: "/usr/sbin:/usr/bin:/sbin:/bin" },
|
|
132
|
-
shell: false,
|
|
133
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
134
|
-
});
|
|
135
|
-
if (result.error || result.status !== 0 || result.signal || result.stderr?.trim()) {
|
|
136
|
-
throw new Error("service account provisioning failed");
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
export function buildServiceAccountProvisioningCommands(accounts) {
|
|
141
|
-
if (!Array.isArray(accounts) || accounts.some((account) =>
|
|
142
|
-
!SAFE_ID.test(account?.name ?? "") || !Number.isSafeInteger(account?.uid) || account.uid < 1 ||
|
|
143
|
-
!Number.isSafeInteger(account?.gid) || account.gid < 1 ||
|
|
144
|
-
!["/var/lib/sellable-agent-worker", "/var/lib/sellable-agent-runtime", "/var/empty"].includes(account?.home) ||
|
|
145
|
-
typeof account.createUser !== "boolean" || typeof account.createGroup !== "boolean"
|
|
146
|
-
)) throw new Error("service account command input rejected");
|
|
147
|
-
return [
|
|
148
|
-
...accounts.filter((account) => account.createGroup).map((account) => ({
|
|
149
|
-
command: "/usr/sbin/groupadd",
|
|
150
|
-
args: ["--gid", String(account.gid), account.name],
|
|
151
|
-
})),
|
|
152
|
-
...accounts.filter((account) => account.createUser).map((account) => ({
|
|
153
|
-
command: "/usr/sbin/useradd",
|
|
154
|
-
args: [
|
|
155
|
-
"--uid", String(account.uid), "--gid", account.name,
|
|
156
|
-
"--home-dir", account.home, "--shell", "/usr/sbin/nologin", "--no-create-home", account.name,
|
|
157
|
-
],
|
|
158
|
-
})),
|
|
159
|
-
];
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
function provisionServiceAccounts(input, checked) {
|
|
163
|
-
const initial = accountDatabaseState(
|
|
164
|
-
checked.targetRoot, checked.expectedAccounts, checked.uid, checked.gid,
|
|
165
|
-
);
|
|
166
|
-
const missing = initial.filter((account) => !account.userPresent || !account.groupPresent);
|
|
167
|
-
if (missing.length === 0) {
|
|
168
|
-
return readAccountDatabase(checked.targetRoot, checked.expectedAccounts, checked.uid, checked.gid);
|
|
169
|
-
}
|
|
170
|
-
if (checked.targetRoot !== "/") {
|
|
171
|
-
if (input.disposableFixture !== true || typeof input.accountProvisioner !== "function") {
|
|
172
|
-
throw new Error("fixture service account provisioner missing");
|
|
173
|
-
}
|
|
174
|
-
input.accountProvisioner({
|
|
175
|
-
targetRoot: checked.targetRoot,
|
|
176
|
-
accounts: missing.map(({ name, uid, gid, userPresent, groupPresent, home }) => ({
|
|
177
|
-
name, uid, gid, home, createUser: !userPresent, createGroup: !groupPresent,
|
|
178
|
-
})),
|
|
179
|
-
});
|
|
180
|
-
} else {
|
|
181
|
-
const commands = buildServiceAccountProvisioningCommands(missing.map((account) => ({
|
|
182
|
-
name: account.name,
|
|
183
|
-
uid: account.uid,
|
|
184
|
-
gid: account.gid,
|
|
185
|
-
home: account.home,
|
|
186
|
-
createUser: !account.userPresent,
|
|
187
|
-
createGroup: !account.groupPresent,
|
|
188
|
-
})));
|
|
189
|
-
for (const command of commands) {
|
|
190
|
-
runAccountCommand(command.command, command.args);
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
return readAccountDatabase(checked.targetRoot, checked.expectedAccounts, checked.uid, checked.gid);
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
function validateInstallInput(input) {
|
|
197
|
-
const targetRoot = resolve(input.targetRoot ?? "/");
|
|
198
|
-
if (!isAbsolute(targetRoot) || !existsSync(targetRoot) || lstatSync(targetRoot).isSymbolicLink()) {
|
|
199
|
-
throw new Error("service install root rejected");
|
|
200
|
-
}
|
|
201
|
-
if (targetRoot !== "/" && input.disposableFixture !== true) throw new Error("non-host install root rejected");
|
|
202
|
-
const packageRoot = realpathSync(input.packageRoot ?? PACKAGE_ROOT);
|
|
203
|
-
if (
|
|
204
|
-
packageRoot !== realpathSync(PACKAGE_ROOT) &&
|
|
205
|
-
!(input.disposableFixture === true && targetRoot !== "/")
|
|
206
|
-
) throw new Error("service package root pin rejected");
|
|
207
|
-
const uid = input.ownerUid ?? process.getuid?.();
|
|
208
|
-
const gid = input.ownerGid ?? process.getgid?.();
|
|
209
|
-
if (!Number.isSafeInteger(uid) || uid < 0 || !Number.isSafeInteger(gid) || gid < 0) {
|
|
210
|
-
throw new Error("service install owner rejected");
|
|
211
|
-
}
|
|
212
|
-
if (targetRoot === "/" && (uid !== 0 || gid !== 0 || process.getuid?.() !== 0)) {
|
|
213
|
-
throw new Error("live service install requires root");
|
|
214
|
-
}
|
|
215
|
-
const targetRootInfo = lstatSync(targetRoot);
|
|
216
|
-
if (
|
|
217
|
-
!targetRootInfo.isDirectory() || targetRootInfo.uid !== uid || targetRootInfo.gid !== gid ||
|
|
218
|
-
(targetRootInfo.mode & 0o022) !== 0
|
|
219
|
-
) throw new Error("service install root ownership rejected");
|
|
220
|
-
const proxy = validateRuntimeEgressProxyConfig(input.egressProxyConfig);
|
|
221
|
-
if (!input.workerConfig || !input.helperConfig || !proxy.ok || SECRET.test(JSON.stringify({
|
|
222
|
-
workerConfig: input.workerConfig,
|
|
223
|
-
helperConfig: input.helperConfig,
|
|
224
|
-
egressProxyConfig: input.egressProxyConfig,
|
|
225
|
-
}))) throw new Error("service config rejected");
|
|
226
|
-
if (
|
|
227
|
-
!SAFE_ID.test(input.workerConfig.workerId ?? "") || !SAFE_ID.test(input.workerConfig.hostId ?? "") ||
|
|
228
|
-
!Number.isSafeInteger(input.workerConfig.workerUid) || input.workerConfig.workerUid < 1 ||
|
|
229
|
-
!Number.isSafeInteger(input.workerConfig.workerGid) || input.workerConfig.workerGid < 1
|
|
230
|
-
) {
|
|
231
|
-
throw new Error("worker config identity rejected");
|
|
232
|
-
}
|
|
233
|
-
const profiles = Object.entries(input.helperConfig.profiles ?? {});
|
|
234
|
-
if (profiles.length !== 1 || profiles.some(([profileId, profile]) =>
|
|
235
|
-
!SAFE_ID.test(profileId) || !Number.isSafeInteger(profile.runtimeUid) || profile.runtimeUid < 1 ||
|
|
236
|
-
!Number.isSafeInteger(profile.runtimeGid) || profile.runtimeGid < 1 ||
|
|
237
|
-
profile.workerUid !== input.workerConfig.workerUid || profile.workerGid !== input.workerConfig.workerGid ||
|
|
238
|
-
profile.proxyUid !== proxy.config.uid || profile.proxyGid !== proxy.config.gid ||
|
|
239
|
-
profile.proxyExecutablePath !== "/usr/local/lib/sellable-agent/package/bin/sellable-agent-egress-proxy.mjs" ||
|
|
240
|
-
profile.installedPackageManifestPath !== IMMUTABLE_MANIFEST_PATH ||
|
|
241
|
-
profile.externalRuntimeManifestPath !== EXTERNAL_RUNTIME_MANIFEST_PATH ||
|
|
242
|
-
profile.externalRuntimeClosureRoot !== EXTERNAL_RUNTIME_CLOSURE_ROOT ||
|
|
243
|
-
profile.externalRuntimeTrustRoot !== dirname(EXTERNAL_RUNTIME_CLOSURE_ROOT) ||
|
|
244
|
-
profile.externalRuntimeOwnerUid !== uid || profile.externalRuntimeOwnerGid !== gid ||
|
|
245
|
-
profile.mcpCommand !== EXTERNAL_MCP_ENTRYPOINT ||
|
|
246
|
-
profile.hermesExecutablePath !== EXTERNAL_HERMES_ENTRYPOINT ||
|
|
247
|
-
profile.hermesSourceRoot !== `${EXTERNAL_RUNTIME_CLOSURE_ROOT}/hermes` ||
|
|
248
|
-
profile.proxyConfigPath !== "/etc/sellable-agent/egress-proxy.json" ||
|
|
249
|
-
profile.runtimeBoundaryConfigPath !== "/etc/sellable-agent/runtime-boundary.json" ||
|
|
250
|
-
profile.providerCredentialSourcePath !== "/var/lib/sellable-agent-bootstrap/provider-auth" ||
|
|
251
|
-
!SHA256.test(profile.providerCredentialFingerprint ?? "") ||
|
|
252
|
-
profile.runtimeServicePath !== "/etc/services.d/sellable-agent-runtime" ||
|
|
253
|
-
profile.runtimeServiceEnvRoot !== "/etc/services.d/sellable-agent-runtime/env" ||
|
|
254
|
-
new Set([profile.runtimeUid, profile.proxyUid, profile.workerUid]).size !== 3
|
|
255
|
-
)) throw new Error("helper profile map rejected");
|
|
256
|
-
const profile = profiles[0][1];
|
|
257
|
-
const externalRuntime = observeExternalRuntimeClosure({
|
|
258
|
-
manifestPath: profile.externalRuntimeManifestPath,
|
|
259
|
-
pathRoot: targetRoot,
|
|
260
|
-
trustRoot: profile.externalRuntimeTrustRoot,
|
|
261
|
-
expectedClosureRoot: profile.externalRuntimeClosureRoot,
|
|
262
|
-
ownerUid: uid,
|
|
263
|
-
ownerGid: gid,
|
|
264
|
-
expectedEntrypoints: { mcp: profile.mcpCommand, hermes: profile.hermesExecutablePath },
|
|
265
|
-
});
|
|
266
|
-
const expectedAccounts = [
|
|
267
|
-
{
|
|
268
|
-
name: "sellable-agent-worker", uid: input.workerConfig.workerUid,
|
|
269
|
-
gid: input.workerConfig.workerGid, home: "/var/lib/sellable-agent-worker",
|
|
270
|
-
},
|
|
271
|
-
{
|
|
272
|
-
name: "sellable-agent-runtime", uid: profile.runtimeUid,
|
|
273
|
-
gid: profile.runtimeGid, home: "/var/lib/sellable-agent-runtime",
|
|
274
|
-
},
|
|
275
|
-
{
|
|
276
|
-
name: "sellable-agent-egress-proxy", uid: proxy.config.uid,
|
|
277
|
-
gid: proxy.config.gid, home: "/var/empty",
|
|
278
|
-
},
|
|
279
|
-
];
|
|
280
|
-
return {
|
|
281
|
-
targetRoot: realpathSync(targetRoot), packageRoot, uid, gid, proxyConfig: proxy.config,
|
|
282
|
-
expectedAccounts, externalRuntime,
|
|
283
|
-
};
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
function target(root, absolutePath) {
|
|
287
|
-
const value = join(root, absolutePath.replace(/^\/+/, ""));
|
|
288
|
-
if (!inside(root, value)) throw new Error("service target confinement failed");
|
|
289
|
-
return value;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
function installedRuntimeLauncher(name) {
|
|
293
|
-
return `#!/bin/sh\nexec /usr/bin/node '${IMMUTABLE_PACKAGE_ROOT}/bin/${name}.mjs' "$@"\n`;
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
function configurationFiles(input, checked) {
|
|
297
|
-
const helperRequestRoot = input.workerConfig.helperRequestRoot;
|
|
298
|
-
if (!isAbsolute(helperRequestRoot) || /[\s,*?\[\]{}]/.test(helperRequestRoot)) {
|
|
299
|
-
throw new Error("helper request sudoers path rejected");
|
|
300
|
-
}
|
|
301
|
-
const sudoers = [
|
|
302
|
-
"Defaults:sellable-agent-worker !setenv,secure_path=/usr/local/bin:/usr/bin:/bin",
|
|
303
|
-
`sellable-agent-worker ALL=(root) NOPASSWD: /usr/local/bin/sellable-agent-runtime-helper --request ${helperRequestRoot}/*`,
|
|
304
|
-
"",
|
|
305
|
-
].join("\n");
|
|
306
|
-
return [
|
|
307
|
-
["/etc/sellable-agent/egress-proxy.json", `${JSON.stringify(checked.proxyConfig, null, 2)}\n`, 0o444],
|
|
308
|
-
["/etc/sellable-agent/worker.json", `${JSON.stringify(input.workerConfig, null, 2)}\n`, 0o600],
|
|
309
|
-
["/etc/sellable-agent/runtime-helper.json", `${JSON.stringify(input.helperConfig, null, 2)}\n`, 0o600],
|
|
310
|
-
["/etc/sudoers.d/sellable-agent-runtime-helper", sudoers, 0o440],
|
|
311
|
-
].map(([logicalPath, bytes, mode]) => ({ logicalPath, bytes, mode }));
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
function installationFiles(input, checked) {
|
|
315
|
-
return [
|
|
316
|
-
...AGENT_BIN_FILES.map((name) => [
|
|
317
|
-
`${IMMUTABLE_PACKAGE_ROOT}/bin/${name}`, readFileSync(join(checked.packageRoot, "bin", name)), 0o555,
|
|
318
|
-
]),
|
|
319
|
-
...AGENT_LIB_FILES.map((name) => [
|
|
320
|
-
`${IMMUTABLE_PACKAGE_ROOT}/lib/sellable-agent/${name}`,
|
|
321
|
-
readFileSync(join(checked.packageRoot, "lib/sellable-agent", name)), 0o555,
|
|
322
|
-
]),
|
|
323
|
-
[`${IMMUTABLE_PACKAGE_ROOT}/package.json`, readFileSync(join(checked.packageRoot, "package.json")), 0o444],
|
|
324
|
-
["/usr/local/bin/sellable-agent-egress-proxy", installedRuntimeLauncher("sellable-agent-egress-proxy"), 0o555],
|
|
325
|
-
["/usr/local/bin/sellable-agent-host-worker", installedRuntimeLauncher("sellable-agent-host-worker"), 0o555],
|
|
326
|
-
["/usr/local/bin/sellable-agent-runtime-helper", installedRuntimeLauncher("sellable-agent-runtime-helper"), 0o555],
|
|
327
|
-
["/usr/local/bin/sellable-agent-runtime-launcher", installedRuntimeLauncher("sellable-agent-runtime-launcher"), 0o555],
|
|
328
|
-
["/usr/local/bin/sellable-agent-runtime-probe", installedRuntimeLauncher("sellable-agent-runtime-probe"), 0o555],
|
|
329
|
-
["/usr/local/bin/sellable-agent-sandbox-init", installedRuntimeLauncher("sellable-agent-sandbox-init"), 0o555],
|
|
330
|
-
...configurationFiles(input, checked).map((file) => [file.logicalPath, file.bytes, file.mode]),
|
|
331
|
-
["/etc/services.d/sellable-agent-runtime/down", "", 0o444],
|
|
332
|
-
["/etc/services.d/sellable-agent-egress-proxy/run", readFileSync(join(checked.packageRoot, "services/s6/sellable-agent-egress-proxy/run")), 0o555],
|
|
333
|
-
["/etc/services.d/sellable-agent-runtime/run", readFileSync(join(checked.packageRoot, "services/s6/sellable-agent-runtime/run")), 0o555],
|
|
334
|
-
["/etc/services.d/sellable-agent-host-worker/run", readFileSync(join(checked.packageRoot, "services/s6/sellable-agent-host-worker/run")), 0o555],
|
|
335
|
-
].map(([logicalPath, bytes, mode]) => ({ logicalPath, bytes, mode }));
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
function manifestFile(files) {
|
|
339
|
-
const manifest = {
|
|
340
|
-
version: IMMUTABLE_MANIFEST_VERSION,
|
|
341
|
-
files: files.map((file) => ({
|
|
342
|
-
path: file.logicalPath,
|
|
343
|
-
mode: file.mode,
|
|
344
|
-
digest: sha256(Buffer.from(file.bytes)),
|
|
345
|
-
})),
|
|
346
|
-
};
|
|
347
|
-
return {
|
|
348
|
-
logicalPath: IMMUTABLE_MANIFEST_PATH,
|
|
349
|
-
bytes: `${JSON.stringify(manifest, null, 2)}\n`,
|
|
350
|
-
mode: 0o444,
|
|
351
|
-
};
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
function readInstalledManifest(checked) {
|
|
355
|
-
const pathValue = target(checked.targetRoot, IMMUTABLE_MANIFEST_PATH);
|
|
356
|
-
const link = lstatSync(pathValue);
|
|
357
|
-
const expectedPaths = [
|
|
358
|
-
...AGENT_BIN_FILES.map((name) => `${IMMUTABLE_PACKAGE_ROOT}/bin/${name}`),
|
|
359
|
-
...AGENT_LIB_FILES.map((name) => `${IMMUTABLE_PACKAGE_ROOT}/lib/sellable-agent/${name}`),
|
|
360
|
-
`${IMMUTABLE_PACKAGE_ROOT}/package.json`,
|
|
361
|
-
...AGENT_BIN_FILES.map((name) => `/usr/local/bin/${name.replace(/\.mjs$/, "")}`),
|
|
362
|
-
"/etc/sellable-agent/egress-proxy.json",
|
|
363
|
-
"/etc/sellable-agent/worker.json",
|
|
364
|
-
"/etc/sellable-agent/runtime-helper.json",
|
|
365
|
-
"/etc/sudoers.d/sellable-agent-runtime-helper",
|
|
366
|
-
"/etc/services.d/sellable-agent-runtime/down",
|
|
367
|
-
"/etc/services.d/sellable-agent-egress-proxy/run",
|
|
368
|
-
"/etc/services.d/sellable-agent-runtime/run",
|
|
369
|
-
"/etc/services.d/sellable-agent-host-worker/run",
|
|
370
|
-
].sort();
|
|
371
|
-
if (
|
|
372
|
-
link.isSymbolicLink() || !link.isFile() || link.uid !== checked.uid || link.gid !== checked.gid ||
|
|
373
|
-
(link.mode & 0o777) !== 0o444
|
|
374
|
-
) throw new Error("installed manifest rejected");
|
|
375
|
-
const bytes = readFileSync(pathValue);
|
|
376
|
-
const parsed = JSON.parse(bytes.toString("utf8"));
|
|
377
|
-
if (
|
|
378
|
-
parsed?.version !== IMMUTABLE_MANIFEST_VERSION || !Array.isArray(parsed.files) ||
|
|
379
|
-
parsed.files.some((file) =>
|
|
380
|
-
typeof file?.path !== "string" || !file.path.startsWith("/") ||
|
|
381
|
-
!Number.isSafeInteger(file.mode) || !SHA256.test(file.digest ?? "")
|
|
382
|
-
) || new Set(parsed.files.map((file) => file.path)).size !== parsed.files.length ||
|
|
383
|
-
JSON.stringify(parsed.files.map((file) => file.path).sort()) !== JSON.stringify(expectedPaths)
|
|
384
|
-
) throw new Error("installed manifest schema rejected");
|
|
385
|
-
return { bytes, files: parsed.files };
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
export function observeAgentHostServices(input) {
|
|
389
|
-
try {
|
|
390
|
-
const checked = validateInstallInput(input);
|
|
391
|
-
const accounts = readAccountDatabase(
|
|
392
|
-
checked.targetRoot, checked.expectedAccounts, checked.uid, checked.gid,
|
|
393
|
-
);
|
|
394
|
-
const installedManifest = readInstalledManifest(checked);
|
|
395
|
-
const files = installedManifest.files.map((file) => {
|
|
396
|
-
const pathValue = target(checked.targetRoot, file.path);
|
|
397
|
-
const link = lstatSync(pathValue);
|
|
398
|
-
const bytes = readFileSync(pathValue);
|
|
399
|
-
if (
|
|
400
|
-
link.isSymbolicLink() || !link.isFile() || link.uid !== checked.uid || link.gid !== checked.gid ||
|
|
401
|
-
(link.mode & 0o777) !== file.mode || sha256(bytes) !== file.digest
|
|
402
|
-
) throw new Error("installed service readback rejected");
|
|
403
|
-
return { path: file.path, mode: file.mode, uid: link.uid, gid: link.gid, digest: sha256(bytes) };
|
|
404
|
-
});
|
|
405
|
-
const configuration = configurationFiles(input, checked);
|
|
406
|
-
for (const file of configuration) {
|
|
407
|
-
const bytes = readFileSync(target(checked.targetRoot, file.logicalPath));
|
|
408
|
-
if (!bytes.equals(Buffer.from(file.bytes))) throw new Error("installed configuration drift rejected");
|
|
409
|
-
}
|
|
410
|
-
return {
|
|
411
|
-
ok: true,
|
|
412
|
-
producer: "sellable-agent-service-installer",
|
|
413
|
-
accounts,
|
|
414
|
-
manifestDigest: sha256(installedManifest.bytes),
|
|
415
|
-
externalRuntimeClosureDigest: checked.externalRuntime.closureDigest,
|
|
416
|
-
files: [
|
|
417
|
-
{ path: IMMUTABLE_MANIFEST_PATH, mode: 0o444, uid: checked.uid, gid: checked.gid, digest: sha256(installedManifest.bytes) },
|
|
418
|
-
...files,
|
|
419
|
-
],
|
|
420
|
-
};
|
|
421
|
-
} catch {
|
|
422
|
-
return { ok: false, code: "service_readback_rejected" };
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
export function installAgentHostServices(input) {
|
|
427
|
-
try {
|
|
428
|
-
const checked = validateInstallInput(input);
|
|
429
|
-
provisionServiceAccounts(input, checked);
|
|
430
|
-
const files = installationFiles(input, checked);
|
|
431
|
-
for (const file of [...files, manifestFile(files)]) {
|
|
432
|
-
atomicOwnedFile(
|
|
433
|
-
checked.targetRoot, target(checked.targetRoot, file.logicalPath),
|
|
434
|
-
file.bytes, file.mode, checked.uid, checked.gid,
|
|
435
|
-
);
|
|
436
|
-
}
|
|
437
|
-
return observeAgentHostServices(input);
|
|
438
|
-
} catch {
|
|
439
|
-
return { ok: false, code: "service_install_rejected" };
|
|
440
|
-
}
|
|
441
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
#!/bin/sh
|
|
2
|
-
set -eu
|
|
3
|
-
|
|
4
|
-
exec /usr/bin/setpriv \
|
|
5
|
-
--reuid sellable-agent-egress-proxy \
|
|
6
|
-
--regid sellable-agent-egress-proxy \
|
|
7
|
-
--clear-groups \
|
|
8
|
-
--no-new-privs \
|
|
9
|
-
--bounding-set=-all \
|
|
10
|
-
--inh-caps=-all \
|
|
11
|
-
--ambient-caps=-all \
|
|
12
|
-
/usr/bin/env -i \
|
|
13
|
-
PATH=/usr/local/bin:/usr/bin:/bin \
|
|
14
|
-
NODE_ENV=production \
|
|
15
|
-
/usr/bin/node /usr/local/lib/sellable-agent/package/bin/sellable-agent-egress-proxy.mjs
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
#!/bin/sh
|
|
2
|
-
set -eu
|
|
3
|
-
|
|
4
|
-
if [ "${SELLABLE_AGENT_RUNTIME_ENV_LOADED:-}" != "1" ]; then
|
|
5
|
-
exec /command/s6-envdir /etc/services.d/sellable-agent-runtime/env "$0"
|
|
6
|
-
fi
|
|
7
|
-
|
|
8
|
-
: "${SELLABLE_AGENT_PROFILE_ID:?profile id required}"
|
|
9
|
-
: "${SELLABLE_AGENT_WORKER_SLACK_SECRET_ROOT:?worker Slack secret root required}"
|
|
10
|
-
|
|
11
|
-
SELLABLE_AGENT_SLACK_SECRET_ROOT="${SELLABLE_AGENT_WORKER_SLACK_SECRET_ROOT}/${SELLABLE_AGENT_PROFILE_ID}"
|
|
12
|
-
|
|
13
|
-
exec 8<"${SELLABLE_AGENT_SLACK_SECRET_ROOT}/slack-app"
|
|
14
|
-
exec 9<"${SELLABLE_AGENT_SLACK_SECRET_ROOT}/slack-bot"
|
|
15
|
-
|
|
16
|
-
exec /usr/bin/env -i \
|
|
17
|
-
PATH=/usr/local/bin:/usr/bin:/bin \
|
|
18
|
-
NODE_ENV=production \
|
|
19
|
-
/usr/local/bin/sellable-agent-runtime-launcher
|