@ouro.bot/cli 0.1.0-alpha.816 → 0.1.0-alpha.817

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.
Files changed (49) hide show
  1. package/assets/sanctuary-host-launcher.sh +16 -0
  2. package/changelog.json +10 -0
  3. package/deploy/unraid/Dockerfile +6 -0
  4. package/deploy/unraid/README.txt +280 -60
  5. package/deploy/unraid/docker-man-template-transaction.mjs +192 -7
  6. package/deploy/unraid/sanctuary-acceptance-adapter.sh +1 -1
  7. package/deploy/unraid/sanctuary-acceptance-contract.json +7 -7
  8. package/deploy/unraid/sanctuary-authority-installation.json +36 -0
  9. package/deploy/unraid/sanctuary-authority-service.sh +30 -0
  10. package/deploy/unraid/sanctuary-unit16-host-broker.mjs +62 -9
  11. package/deploy/unraid/sanctuary-unit16-run.sh +15 -15
  12. package/deploy/unraid/sanctuary.ouro/bundle-meta.json +1 -1
  13. package/deploy/unraid/sanctuary.ouro/tool-profiles.json +2 -2
  14. package/deploy/unraid/sanctuary.xml +2 -1
  15. package/dist/heart/core.js +2 -1
  16. package/dist/heart/daemon/container-spec-auditor-main.js +8 -7
  17. package/dist/heart/daemon/container-spec-auditor.js +16 -9
  18. package/dist/heart/daemon/sanctuary-acceptance-adapter.js +95 -91
  19. package/dist/heart/daemon/sanctuary-acceptance-harness.js +95 -169
  20. package/dist/heart/daemon/sanctuary-acceptance-scenarios.js +4 -5
  21. package/dist/heart/daemon/sanctuary-authority-codec.js +86 -0
  22. package/dist/heart/daemon/sanctuary-authority-epoch.js +256 -0
  23. package/dist/heart/daemon/sanctuary-authority-installation.js +140 -0
  24. package/dist/heart/daemon/sanctuary-authority-ledger.js +241 -0
  25. package/dist/heart/daemon/sanctuary-authority-root-lifecycle.js +780 -0
  26. package/dist/heart/daemon/sanctuary-authority-vault-migration.js +79 -0
  27. package/dist/heart/daemon/sanctuary-host-authority.js +1008 -0
  28. package/dist/heart/daemon/sanctuary-host-detached-supervisor.js +494 -0
  29. package/dist/heart/daemon/sanctuary-host-executor.js +670 -0
  30. package/dist/heart/daemon/sanctuary-host-linux-kernel.js +334 -0
  31. package/dist/heart/daemon/sanctuary-host-supervisor-entry.js +207 -0
  32. package/dist/heart/daemon/sanctuary-host-supervisor.js +95 -0
  33. package/dist/heart/daemon/sanctuary-telegram-authority-entry.js +445 -0
  34. package/dist/heart/daemon/sanctuary-telegram-authority-gateway.js +585 -0
  35. package/dist/heart/daemon/sanctuary-telegram-authority-service.js +615 -0
  36. package/dist/heart/daemon/sense-manager.js +20 -10
  37. package/dist/repertoire/tools-sanctuary-host.js +202 -0
  38. package/dist/repertoire/tools.js +15 -6
  39. package/dist/senses/root-host-approval-port.js +345 -0
  40. package/dist/senses/root-host-approval-runtime.js +393 -0
  41. package/dist/senses/sanctuary-authority-resident.js +102 -0
  42. package/dist/senses/telegram-admission.js +7 -0
  43. package/dist/senses/telegram-attachments.js +5 -1
  44. package/dist/senses/telegram-authority-transport.js +231 -0
  45. package/dist/senses/telegram-client.js +31 -8
  46. package/dist/senses/telegram-entry.js +1 -1
  47. package/dist/senses/telegram.js +168 -28
  48. package/npm-shrinkwrap.json +2 -2
  49. package/package.json +1 -1
@@ -0,0 +1,445 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
+ if (k2 === undefined) k2 = k;
5
+ var desc = Object.getOwnPropertyDescriptor(m, k);
6
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
+ desc = { enumerable: true, get: function() { return m[k]; } };
8
+ }
9
+ Object.defineProperty(o, k2, desc);
10
+ }) : (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ o[k2] = m[k];
13
+ }));
14
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
16
+ }) : function(o, v) {
17
+ o["default"] = v;
18
+ });
19
+ var __importStar = (this && this.__importStar) || (function () {
20
+ var ownKeys = function(o) {
21
+ ownKeys = Object.getOwnPropertyNames || function (o) {
22
+ var ar = [];
23
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
24
+ return ar;
25
+ };
26
+ return ownKeys(o);
27
+ };
28
+ return function (mod) {
29
+ if (mod && mod.__esModule) return mod;
30
+ var result = {};
31
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
32
+ __setModuleDefault(result, mod);
33
+ return result;
34
+ };
35
+ })();
36
+ Object.defineProperty(exports, "__esModule", { value: true });
37
+ exports.loadSanctuaryTelegramAuthorityConfig = loadSanctuaryTelegramAuthorityConfig;
38
+ exports.startSanctuaryTelegramAuthority = startSanctuaryTelegramAuthority;
39
+ exports.runSanctuaryTelegramAuthorityCli = runSanctuaryTelegramAuthorityCli;
40
+ const node_crypto_1 = require("node:crypto");
41
+ const fs = __importStar(require("node:fs"));
42
+ const path = __importStar(require("node:path"));
43
+ const runtime_1 = require("../../nerves/runtime");
44
+ const telegram_client_1 = require("../../senses/telegram-client");
45
+ const sanctuary_telegram_authority_gateway_1 = require("./sanctuary-telegram-authority-gateway");
46
+ const sanctuary_host_authority_1 = require("./sanctuary-host-authority");
47
+ const sanctuary_authority_ledger_1 = require("./sanctuary-authority-ledger");
48
+ const sanctuary_authority_codec_1 = require("./sanctuary-authority-codec");
49
+ const sanctuary_host_detached_supervisor_1 = require("./sanctuary-host-detached-supervisor");
50
+ const sanctuary_host_executor_1 = require("./sanctuary-host-executor");
51
+ const sanctuary_authority_installation_1 = require("./sanctuary-authority-installation");
52
+ const sanctuary_authority_epoch_1 = require("./sanctuary-authority-epoch");
53
+ const sanctuary_telegram_authority_service_1 = require("./sanctuary-telegram-authority-service");
54
+ const MAX_PRIVATE_FILE_BYTES = 64 * 1024;
55
+ const MAX_ATTACHMENT_BYTES = 20_000_000;
56
+ function isObject(value) {
57
+ return typeof value === "object" && value !== null && !Array.isArray(value);
58
+ }
59
+ function exactKeys(value, keys) {
60
+ const actual = Object.keys(value).sort();
61
+ const expected = [...keys].sort();
62
+ return actual.length === expected.length && actual.every((key, index) => key === expected[index]);
63
+ }
64
+ function readPrivateFile(filePath, expectedUid) {
65
+ if (!path.isAbsolute(filePath))
66
+ throw new Error("Sanctuary Telegram authority private path must be absolute");
67
+ const handle = fs.openSync(filePath, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW);
68
+ try {
69
+ const stat = fs.fstatSync(handle);
70
+ if (!stat.isFile() || stat.nlink !== 1 || stat.uid !== expectedUid || stat.gid !== (expectedUid === 0 ? 0 : process.getgid())
71
+ || (stat.mode & 0o7777) !== 0o600 || fs.realpathSync(filePath) !== filePath || stat.size < 1 || stat.size > MAX_PRIVATE_FILE_BYTES)
72
+ throw new Error("Sanctuary Telegram authority private file is unsafe");
73
+ return fs.readFileSync(handle, "utf8");
74
+ }
75
+ finally {
76
+ fs.closeSync(handle);
77
+ }
78
+ }
79
+ function loadSanctuaryTelegramAuthorityConfig(configPath, options = {}) {
80
+ const expectedUid = options.expectedUid ?? 0;
81
+ const value = JSON.parse(readPrivateFile(configPath, expectedUid));
82
+ const keys = [
83
+ "schemaVersion", "agentRoot", "targetHost", "botId", "ownerUserId", "ownerChatId", "keyId",
84
+ "publicKeyDigest", "tokenPath", "privateKeyPath", "socketPath", "socketGroupId", "readinessPath", "lockPath",
85
+ "hostStagingRoot", "hostExecutionStateRoot", "hostSupervisorStateRoot", "hostCgroupRoot", "hostSupervisorProgramPath",
86
+ "hostSupervisorProgramDigest", "hostLauncherPath", "hostLauncherDigest", "hostPrlimitPath",
87
+ "hostPrlimitDigest", "hostSetsidPath", "hostSetsidDigest",
88
+ "hostShellPath", "hostShellDigest",
89
+ "epochRoot", "packageRoot", "packageManifestPath", "packageManifestDigest",
90
+ ];
91
+ if (!isObject(value)
92
+ || !exactKeys(value, keys)
93
+ || value.schemaVersion !== 1
94
+ || ![
95
+ "agentRoot", "tokenPath", "privateKeyPath", "socketPath", "readinessPath", "lockPath",
96
+ "hostStagingRoot", "hostExecutionStateRoot", "hostSupervisorStateRoot", "hostCgroupRoot", "hostSupervisorProgramPath",
97
+ "hostLauncherPath", "hostPrlimitPath", "hostSetsidPath",
98
+ "hostShellPath",
99
+ "epochRoot", "packageRoot", "packageManifestPath",
100
+ ]
101
+ .every((key) => typeof value[key] === "string" && path.isAbsolute(value[key]))
102
+ || !["targetHost", "keyId"].every((key) => typeof value[key] === "string" && value[key].length > 0)
103
+ || !["botId", "ownerUserId", "ownerChatId"].every((key) => typeof value[key] === "string" && /^[1-9][0-9]*$/u.test(value[key]))
104
+ || typeof value.publicKeyDigest !== "string"
105
+ || !/^sha256:[a-f0-9]{64}$/u.test(value.publicKeyDigest)
106
+ || !["hostSupervisorProgramDigest", "hostLauncherDigest", "hostPrlimitDigest", "hostSetsidDigest", "hostShellDigest", "packageManifestDigest"]
107
+ .every((key) => typeof value[key] === "string" && /^sha256:[a-f0-9]{64}$/u.test(value[key]))
108
+ || !Number.isSafeInteger(value.socketGroupId)
109
+ || value.socketGroupId < 1) {
110
+ throw new Error("Sanctuary Telegram authority configuration is invalid");
111
+ }
112
+ readPrivateFile(value.tokenPath, expectedUid);
113
+ readPrivateFile(value.privateKeyPath, expectedUid);
114
+ return value;
115
+ }
116
+ function privateDirectory(directory, mode) {
117
+ fs.mkdirSync(directory, { recursive: true, mode });
118
+ const stat = fs.lstatSync(directory);
119
+ if (!stat.isDirectory() || fs.realpathSync(directory) !== directory || (stat.mode & 0o7777) !== mode)
120
+ throw new Error("Sanctuary Telegram authority directory is unsafe");
121
+ }
122
+ function writePrivateJson(filePath, value, mode = 0o600) {
123
+ const directoryMode = mode === 0o600 ? 0o700 : 0o750;
124
+ privateDirectory(path.dirname(filePath), directoryMode);
125
+ const temporaryPath = `${filePath}.${process.pid}.tmp`;
126
+ const handle = fs.openSync(temporaryPath, fs.constants.O_CREAT | fs.constants.O_EXCL | fs.constants.O_WRONLY | fs.constants.O_NOFOLLOW, 0o600);
127
+ try {
128
+ try {
129
+ fs.writeFileSync(handle, `${JSON.stringify(value)}\n`, "utf8");
130
+ fs.fsyncSync(handle);
131
+ }
132
+ finally {
133
+ fs.closeSync(handle);
134
+ }
135
+ fs.renameSync(temporaryPath, filePath);
136
+ }
137
+ catch (error) {
138
+ fs.unlinkSync(temporaryPath);
139
+ throw error;
140
+ }
141
+ fs.chmodSync(filePath, mode);
142
+ const directory = fs.openSync(path.dirname(filePath), fs.constants.O_RDONLY | fs.constants.O_DIRECTORY | fs.constants.O_NOFOLLOW);
143
+ try {
144
+ fs.fsyncSync(directory);
145
+ }
146
+ finally {
147
+ fs.closeSync(directory);
148
+ }
149
+ }
150
+ function acquireProcessLock(lockPath, expectedUid, processAlive) {
151
+ privateDirectory(path.dirname(lockPath), 0o700);
152
+ try {
153
+ const existing = readPrivateFile(lockPath, expectedUid).trim();
154
+ const pid = Number(existing);
155
+ if (Number.isSafeInteger(pid) && pid > 0 && processAlive(pid)) {
156
+ throw new Error("Sanctuary Telegram authority is already running");
157
+ }
158
+ fs.unlinkSync(lockPath);
159
+ }
160
+ catch (error) {
161
+ if (error.code !== "ENOENT") {
162
+ if (error instanceof Error && error.message.includes("already running"))
163
+ throw error;
164
+ throw error;
165
+ }
166
+ }
167
+ const handle = fs.openSync(lockPath, fs.constants.O_CREAT | fs.constants.O_EXCL | fs.constants.O_WRONLY | fs.constants.O_NOFOLLOW, 0o600);
168
+ fs.writeFileSync(handle, String(process.pid), "utf8");
169
+ fs.fsyncSync(handle);
170
+ fs.closeSync(handle);
171
+ return () => fs.rmSync(lockPath, { force: true });
172
+ }
173
+ async function readBoundedFileResponse(response) {
174
+ if (!response.ok)
175
+ throw new Error(`Sanctuary Telegram file download failed with HTTP ${response.status}`);
176
+ const advertised = Number(response.headers.get("content-length"));
177
+ if (Number.isFinite(advertised) && advertised > MAX_ATTACHMENT_BYTES) {
178
+ throw new Error("Sanctuary Telegram file download exceeds its limit");
179
+ }
180
+ if (!response.body)
181
+ throw new Error("Sanctuary Telegram file download is empty");
182
+ const reader = response.body.getReader();
183
+ const chunks = [];
184
+ let total = 0;
185
+ try {
186
+ for (;;) {
187
+ const { done, value } = await reader.read();
188
+ if (done)
189
+ break;
190
+ total += value.byteLength;
191
+ if (total > MAX_ATTACHMENT_BYTES)
192
+ throw new Error("Sanctuary Telegram file download exceeds its limit");
193
+ chunks.push(Buffer.from(value));
194
+ }
195
+ }
196
+ finally {
197
+ await reader.cancel().catch(() => undefined);
198
+ }
199
+ const contentType = response.headers.get("content-type");
200
+ return { body: Buffer.concat(chunks, total), ...(contentType ? { contentType } : {}) };
201
+ }
202
+ async function startSanctuaryTelegramAuthority(options) {
203
+ (0, runtime_1.emitNervesEvent)({ component: "daemon", event: "daemon.sanctuary_authority_boot_requested", message: "Sanctuary authority boot requested; prerequisite verification pending" });
204
+ const expectedUid = options.expectedUid ?? 0;
205
+ if (process.getuid() !== expectedUid) {
206
+ throw new Error("Sanctuary Telegram authority must run as the configured root owner");
207
+ }
208
+ const config = options.loadConfig
209
+ ? options.loadConfig(options.configPath, expectedUid)
210
+ : loadSanctuaryTelegramAuthorityConfig(options.configPath, { expectedUid });
211
+ const expectedGid = expectedUid === 0 ? 0 : process.getgid();
212
+ (0, sanctuary_authority_installation_1.verifySanctuaryAuthorityInstallation)({
213
+ packageRoot: config.packageRoot, manifestPath: config.packageManifestPath, manifestDigest: config.packageManifestDigest,
214
+ stateRoot: config.epochRoot, stagingRoot: config.hostStagingRoot, socketRoot: path.dirname(config.socketPath),
215
+ cgroupRoot: config.hostCgroupRoot, expectedUid, expectedGid, socketGroupId: config.socketGroupId,
216
+ });
217
+ const epoch = (0, sanctuary_authority_epoch_1.readSanctuaryAuthorityEpoch)(config.epochRoot, { expectedUid, expectedGid });
218
+ if (epoch.state !== "prepared" || epoch.epochId !== config.keyId || epoch.botId !== config.botId
219
+ || epoch.ownerUserId !== config.ownerUserId || epoch.ownerChatId !== config.ownerChatId || epoch.publicKeyDigest !== config.publicKeyDigest
220
+ || epoch.tokenPath !== config.tokenPath || epoch.packageDigest !== config.packageManifestDigest)
221
+ throw new Error("Sanctuary authority epoch is retired or does not match its installation");
222
+ if (!options.retireOnly && fs.existsSync(path.join(config.epochRoot, "retiring.json")))
223
+ throw new Error("Sanctuary authority retirement must finish before restart");
224
+ const readPrivateText = options.readPrivateText ?? readPrivateFile;
225
+ const processAlive = options.processAlive ?? ((pid) => {
226
+ try {
227
+ process.kill(pid, 0);
228
+ return true;
229
+ }
230
+ catch (error) {
231
+ return error.code === "EPERM";
232
+ }
233
+ });
234
+ const releaseLock = acquireProcessLock(config.lockPath, expectedUid, processAlive);
235
+ let api;
236
+ let server;
237
+ let service;
238
+ let closed = false;
239
+ try {
240
+ const token = readPrivateText(config.tokenPath, expectedUid).trim();
241
+ if (!/^[1-9][0-9]*:[A-Za-z0-9_-]{20,}$/u.test(token)) {
242
+ throw new Error("Sanctuary Telegram authority token is invalid");
243
+ }
244
+ const privateKey = (0, node_crypto_1.createPrivateKey)(readPrivateText(config.privateKeyPath, expectedUid));
245
+ const publicKey = (0, node_crypto_1.createPublicKey)(privateKey);
246
+ if (!options.retireOnly) {
247
+ api = (options.createApi ?? ((value) => (0, telegram_client_1.createTelegramBotApi)({ token: value })))(token);
248
+ const identity = await api.request("getMe", {});
249
+ if (!isObject(identity) || String(identity.id) !== config.botId) {
250
+ throw new Error("Sanctuary Telegram authority bot identity changed");
251
+ }
252
+ }
253
+ const gateway = new sanctuary_telegram_authority_gateway_1.FileSanctuaryTelegramAuthorityGateway(config.agentRoot, {
254
+ targetHost: config.targetHost,
255
+ botId: config.botId,
256
+ ownerUserId: config.ownerUserId,
257
+ ownerChatId: config.ownerChatId,
258
+ keyId: config.keyId,
259
+ publicKeyDigest: config.publicKeyDigest,
260
+ privateKey,
261
+ now: options.now,
262
+ });
263
+ if (!fs.existsSync((0, sanctuary_telegram_authority_gateway_1.sanctuaryTelegramAuthorityStatePath)(config.agentRoot)))
264
+ throw new Error("Sanctuary authority cursor state is absent");
265
+ if (gateway.cursor() < epoch.predecessorCursor)
266
+ throw new Error("Sanctuary authority cursor precedes its epoch");
267
+ const hostAuthority = new sanctuary_host_authority_1.FileSanctuaryHostAuthority(config.agentRoot, {
268
+ targetHost: config.targetHost,
269
+ botId: config.botId,
270
+ ownerUserId: config.ownerUserId,
271
+ ownerChatId: config.ownerChatId,
272
+ keyId: config.keyId,
273
+ publicKeyDigest: config.publicKeyDigest,
274
+ publicKey,
275
+ privateKey,
276
+ now: options.now,
277
+ resolveOwnerObservation: (input) => gateway.ownerObservation(input),
278
+ });
279
+ hostAuthority.reconcilePrepared();
280
+ const hostExecutor = options.createHostExecutor?.({ config, privateKey, publicKey, expectedUid }) ?? (() => {
281
+ const hostSupervisor = new sanctuary_host_detached_supervisor_1.DetachedSanctuaryHostSupervisor({
282
+ stateRoot: config.hostSupervisorStateRoot,
283
+ cgroupRoot: config.hostCgroupRoot,
284
+ programPath: config.hostSupervisorProgramPath,
285
+ programDigest: config.hostSupervisorProgramDigest,
286
+ launcherPath: config.hostLauncherPath,
287
+ launcherDigest: config.hostLauncherDigest,
288
+ prlimitPath: config.hostPrlimitPath,
289
+ prlimitDigest: config.hostPrlimitDigest,
290
+ setsidPath: config.hostSetsidPath,
291
+ setsidDigest: config.hostSetsidDigest,
292
+ shellPath: config.hostShellPath,
293
+ shellDigest: config.hostShellDigest,
294
+ expectedUid,
295
+ keyId: config.keyId,
296
+ publicKeyPem: publicKey.export({ type: "spki", format: "pem" }).toString(),
297
+ });
298
+ return new sanctuary_host_executor_1.SanctuaryHostPermitExecutor({
299
+ ledger: new sanctuary_authority_ledger_1.FileSanctuaryAuthorityLedger(config.agentRoot),
300
+ expectedTargetHost: config.targetHost,
301
+ expectedOwnerUserId: config.ownerUserId,
302
+ expectedOwnerChatId: config.ownerChatId,
303
+ expectedKeyId: config.keyId,
304
+ expectedPublicKeyDigest: config.publicKeyDigest,
305
+ publicKey,
306
+ privateKey,
307
+ stagingRoot: config.hostStagingRoot,
308
+ stateRoot: config.hostExecutionStateRoot,
309
+ supervisor: hostSupervisor,
310
+ now: options.now,
311
+ });
312
+ })();
313
+ async function reconcileExecutions() {
314
+ for (const receipt of await hostExecutor.reconcile?.() ?? []) {
315
+ const registrationId = String(receipt.payload.registrationId);
316
+ const status = hostAuthority.status(registrationId);
317
+ if (status?.state === "permitted") {
318
+ hostAuthority.completeExecution(registrationId, receipt);
319
+ }
320
+ else if (status?.state !== "executed"
321
+ || !sameSignedReceipt(status.receipt, receipt)) {
322
+ throw new Error("Sanctuary host execution receipt is not incorporated into authority state");
323
+ }
324
+ hostExecutor.acknowledge?.(String(receipt.payload.permitId));
325
+ }
326
+ }
327
+ const ledger = new sanctuary_authority_ledger_1.FileSanctuaryAuthorityLedger(config.agentRoot);
328
+ const control = {
329
+ close: async () => {
330
+ if (closed)
331
+ return;
332
+ closed = true;
333
+ fs.rmSync(config.readinessPath, { force: true });
334
+ await server?.close();
335
+ api?.stop();
336
+ releaseLock();
337
+ },
338
+ retire: async () => {
339
+ writePrivateJson(path.join(config.epochRoot, "retiring.json"), { schemaVersion: 1, keyId: config.keyId });
340
+ fs.rmSync(config.readinessPath, { force: true });
341
+ await server?.close();
342
+ server = undefined;
343
+ await service?.drainHostExecutions();
344
+ api?.stop();
345
+ api = undefined;
346
+ hostAuthority.retireRegistrations(ledger);
347
+ await reconcileExecutions();
348
+ if (hostAuthority.retireRegistrations(ledger).length !== 0
349
+ || fs.readdirSync(config.hostCgroupRoot).some((entry) => fs.lstatSync(path.join(config.hostCgroupRoot, entry)).isDirectory()))
350
+ throw new Error("Sanctuary authority retirement cleanup is unproven");
351
+ writePrivateJson(path.join(config.epochRoot, "retirement.json"), {
352
+ schemaVersion: 1, keyId: config.keyId, publicKeyDigest: config.publicKeyDigest,
353
+ cursor: gateway.cursor(), quiescent: true,
354
+ });
355
+ await control.close();
356
+ },
357
+ };
358
+ if (options.retireOnly) {
359
+ await control.retire();
360
+ return control;
361
+ }
362
+ await reconcileExecutions();
363
+ function sameSignedReceipt(left, right) {
364
+ return isObject(left)
365
+ && typeof left.domain === "string"
366
+ && typeof left.keyId === "string"
367
+ && typeof left.signature === "string"
368
+ && isObject(left.payload)
369
+ && left.domain === right.domain
370
+ && left.keyId === right.keyId
371
+ && left.signature === right.signature
372
+ && (0, sanctuary_authority_codec_1.authorityArtifactDigest)(left.domain, left.payload) === (0, sanctuary_authority_codec_1.authorityArtifactDigest)(right.domain, right.payload);
373
+ }
374
+ const fetchImpl = options.fetch ?? globalThis.fetch;
375
+ service = new sanctuary_telegram_authority_service_1.SanctuaryTelegramAuthorityService({
376
+ api: api,
377
+ gateway,
378
+ hostAuthority,
379
+ hostExecutor,
380
+ downloadFile: async (filePath) => readBoundedFileResponse(await fetchImpl(`https://api.telegram.org/file/bot${token}/${filePath}`, { signal: AbortSignal.timeout(30_000) })),
381
+ });
382
+ server = (options.createServer ?? ((value) => (0, sanctuary_telegram_authority_service_1.createSanctuaryTelegramAuthorityServer)({
383
+ socketPath: value.socketPath,
384
+ dispatch: (method, params) => value.service.dispatch(method, params),
385
+ })))({ socketPath: config.socketPath, service, gateway });
386
+ await server.listen();
387
+ const residentPinsPath = path.join(path.dirname(config.socketPath), "resident.json");
388
+ writePrivateJson(residentPinsPath, {
389
+ schemaVersion: 1, ...gateway.identity(), publicKeyPem: publicKey.export({ type: "spki", format: "pem" }).toString(),
390
+ }, 0o640);
391
+ if (options.setSocketOwnership) {
392
+ options.setSocketOwnership(config);
393
+ }
394
+ else if (expectedUid === 0) {
395
+ ;
396
+ (options.chown ?? fs.chownSync)(path.dirname(config.socketPath), 0, config.socketGroupId);
397
+ (options.chmod ?? fs.chmodSync)(path.dirname(config.socketPath), 0o750);
398
+ (options.chown ?? fs.chownSync)(config.socketPath, 0, config.socketGroupId);
399
+ (options.chmod ?? fs.chmodSync)(config.socketPath, 0o660);
400
+ (options.chown ?? fs.chownSync)(residentPinsPath, 0, config.socketGroupId);
401
+ }
402
+ const startedAt = options.now?.() ?? new Date().toISOString();
403
+ writePrivateJson(config.readinessPath, {
404
+ schemaVersion: 1,
405
+ status: "ready",
406
+ botId: config.botId,
407
+ socketPath: config.socketPath,
408
+ publicKeyDigest: config.publicKeyDigest,
409
+ startedAt,
410
+ });
411
+ return control;
412
+ }
413
+ catch (error) {
414
+ await server?.close().catch(() => undefined);
415
+ api?.stop();
416
+ fs.rmSync(config.readinessPath, { force: true });
417
+ releaseLock();
418
+ throw error;
419
+ }
420
+ }
421
+ async function runSanctuaryTelegramAuthorityCli(options = {}) {
422
+ const argv = options.argv ?? process.argv;
423
+ const configIndex = argv.indexOf("--config");
424
+ const configPath = configIndex >= 0 ? argv[configIndex + 1] : undefined;
425
+ if (!configPath)
426
+ throw new Error("Sanctuary Telegram authority requires --config");
427
+ const authority = await (options.start ?? startSanctuaryTelegramAuthority)({ configPath, ...(argv.includes("--retire-only") ? { retireOnly: true } : {}) });
428
+ const close = async () => {
429
+ await authority.close();
430
+ const exit = options.exit ?? process.exit;
431
+ exit(0);
432
+ };
433
+ const once = options.once ?? ((event, listener) => { process.once(event, listener); });
434
+ once("SIGINT", () => { void close(); });
435
+ once("SIGTERM", () => { void close(); });
436
+ once("SIGUSR2", () => {
437
+ void authority.retire().then(() => (options.exit ?? process.exit)(0)).catch(() => (options.exit ?? process.exit)(1));
438
+ });
439
+ }
440
+ if (process.argv[1] && fs.existsSync(process.argv[1]) && fs.realpathSync(process.argv[1]) === __filename) {
441
+ void runSanctuaryTelegramAuthorityCli().catch((error) => {
442
+ process.stderr.write(`${error instanceof Error ? error.message : "Sanctuary Telegram authority failed"}\n`);
443
+ process.exitCode = 1;
444
+ });
445
+ }