relmio 0.3.0 → 0.4.0

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.
@@ -0,0 +1,1120 @@
1
+ import { createHash, randomBytes as createRandomBytes, randomUUID } from "node:crypto";
2
+ import * as defaultFileSystem from "node:fs/promises";
3
+ import { createServer } from "node:net";
4
+ import { homedir } from "node:os";
5
+ import { basename, dirname, isAbsolute, join, resolve } from "node:path";
6
+
7
+ import {
8
+ createCodexComposeFile,
9
+ createCodexConfig,
10
+ createCodexDockerfile,
11
+ createCodexRequirements,
12
+ createLocalDeploymentPlan,
13
+ createLocalDockerignore,
14
+ createOpenAiGatewayComposeFile,
15
+ createOpenAiGatewayDockerfile,
16
+ validateInstallId,
17
+ validateLocalTarget,
18
+ validatePlatformApiKey,
19
+ } from "../domain/local-endpoints.js";
20
+ import {
21
+ runLocalProcess,
22
+ validateLocalDockerHost,
23
+ } from "../infrastructure/local-process.js";
24
+
25
+ const MANAGED_MARKER = ".managed-by-relmio.json";
26
+ const ROOT_MARKER = ".managed-by-relmio-root.json";
27
+ const MARKER_SCHEMA_VERSION = 2;
28
+ const ROOT_MARKER_SCHEMA_VERSION = 1;
29
+ const COMPOSE_FILENAME = "docker-compose.yml";
30
+ const PROJECTS = Object.freeze({
31
+ "openai-api": Object.freeze({
32
+ projectPrefix: "relmio-openai-api",
33
+ serviceName: "gateway",
34
+ containerPort: 10_531,
35
+ }),
36
+ "codex-chatgpt": Object.freeze({
37
+ projectPrefix: "relmio-codex-chatgpt",
38
+ serviceName: "codex",
39
+ containerPort: 4_500,
40
+ }),
41
+ });
42
+ const DOCKER_SELECTION_VARIABLES = Object.freeze([
43
+ "DOCKER_HOST",
44
+ "DOCKER_CONTEXT",
45
+ "DOCKER_CONFIG",
46
+ "DOCKER_TLS_VERIFY",
47
+ "DOCKER_CERT_PATH",
48
+ "BUILDKIT_HOST",
49
+ ]);
50
+
51
+ function isMissing(error) {
52
+ return error?.code === "ENOENT";
53
+ }
54
+
55
+ function assertSupportedPlatform(platform) {
56
+ if (platform === "win32") {
57
+ throw new Error(
58
+ "Local Docker endpoints are not supported on native Windows in this release.",
59
+ );
60
+ }
61
+ }
62
+
63
+ function validateAbsolutePath(value) {
64
+ if (
65
+ typeof value !== "string" ||
66
+ value.trim() === "" ||
67
+ value.includes("\0") ||
68
+ !isAbsolute(value)
69
+ ) {
70
+ throw new TypeError("Relmio local storage path is invalid.");
71
+ }
72
+ return resolve(value);
73
+ }
74
+
75
+ function validateManagedBase(value) {
76
+ const resolved = validateAbsolutePath(value);
77
+ if (basename(resolved) !== ".relmio") {
78
+ throw new TypeError("Relmio local storage path is invalid.");
79
+ }
80
+ return resolved;
81
+ }
82
+
83
+ function validateInstallDirectory(value, target) {
84
+ const resolved = validateAbsolutePath(value);
85
+ if (
86
+ basename(resolved) !== target ||
87
+ basename(dirname(resolved)) !== "local" ||
88
+ basename(resolve(resolved, "..", "..")) !== ".relmio"
89
+ ) {
90
+ throw new TypeError("The local endpoint install directory is invalid.");
91
+ }
92
+ return resolved;
93
+ }
94
+
95
+ export async function resolveLocalInstallRoot({
96
+ target,
97
+ env = process.env,
98
+ homeDirectory = homedir(),
99
+ fileSystem = defaultFileSystem,
100
+ platform = process.platform,
101
+ } = {}) {
102
+ assertSupportedPlatform(platform);
103
+ const safeTarget = validateLocalTarget(target);
104
+ const configuredHome =
105
+ typeof env.RELMIO_HOME === "string" && env.RELMIO_HOME.trim() !== ""
106
+ ? env.RELMIO_HOME
107
+ : resolve(homeDirectory, ".relmio");
108
+ const requestedHome = validateManagedBase(configuredHome);
109
+ const requestedParent = dirname(requestedHome);
110
+ let canonicalParent;
111
+ try {
112
+ canonicalParent = await fileSystem.realpath(requestedParent);
113
+ } catch {
114
+ throw new Error("The parent of the Relmio local storage directory is invalid.");
115
+ }
116
+ if (canonicalParent !== resolve(requestedParent)) {
117
+ throw new Error(
118
+ "Relmio refuses a local storage path with a symbolic-link ancestor.",
119
+ );
120
+ }
121
+ const relmioHome = join(canonicalParent, ".relmio");
122
+ return join(relmioHome, "local", safeTarget);
123
+ }
124
+
125
+ async function lstatIfExists(fileSystem, path) {
126
+ try {
127
+ return await fileSystem.lstat(path);
128
+ } catch (error) {
129
+ if (isMissing(error)) {
130
+ return null;
131
+ }
132
+ throw new Error("Relmio could not inspect its local managed directory.");
133
+ }
134
+ }
135
+
136
+ function assertDirectoryMetadata(metadata) {
137
+ if (metadata.isSymbolicLink()) {
138
+ throw new Error("Relmio refuses to use a symbolic link in its managed path.");
139
+ }
140
+ if (!metadata.isDirectory()) {
141
+ throw new Error("Relmio local managed path is not a directory.");
142
+ }
143
+ }
144
+
145
+ async function assertRegularManagedMarker(fileSystem, path, errorMessage) {
146
+ const metadata = await lstatIfExists(fileSystem, path);
147
+ if (!metadata || metadata.isSymbolicLink() || !metadata.isFile()) {
148
+ throw new Error(errorMessage);
149
+ }
150
+ }
151
+
152
+ async function inspectManagedRoot({ fileSystem, relmioHome, installRoot, target }) {
153
+ const localRoot = join(relmioHome, "local");
154
+ const homeMetadata = await lstatIfExists(fileSystem, relmioHome);
155
+ if (!homeMetadata) {
156
+ return {
157
+ baseExists: false,
158
+ deploymentMode: "installed",
159
+ marker: null,
160
+ previousPort: null,
161
+ };
162
+ }
163
+ assertDirectoryMetadata(homeMetadata);
164
+
165
+ let rootMarkerContents;
166
+ const rootMarkerPath = join(relmioHome, ROOT_MARKER);
167
+ await assertRegularManagedMarker(
168
+ fileSystem,
169
+ rootMarkerPath,
170
+ "The Relmio local storage directory already exists without a valid managed-root marker. Nothing was changed.",
171
+ );
172
+ try {
173
+ rootMarkerContents = await fileSystem.readFile(rootMarkerPath, "utf8");
174
+ } catch {
175
+ throw new Error(
176
+ "The Relmio local storage directory already exists without a valid managed-root marker. Nothing was changed.",
177
+ );
178
+ }
179
+ try {
180
+ const rootMarker = JSON.parse(rootMarkerContents);
181
+ if (
182
+ rootMarker?.schemaVersion !== ROOT_MARKER_SCHEMA_VERSION ||
183
+ rootMarker?.kind !== "relmio-local-root"
184
+ ) {
185
+ throw new TypeError();
186
+ }
187
+ } catch {
188
+ throw new Error(
189
+ "The Relmio local storage managed-root marker is invalid. Nothing was changed.",
190
+ );
191
+ }
192
+
193
+ for (const path of [localRoot, installRoot]) {
194
+ const metadata = await lstatIfExists(fileSystem, path);
195
+ if (metadata) {
196
+ assertDirectoryMetadata(metadata);
197
+ }
198
+ }
199
+
200
+ const installMetadata = await lstatIfExists(fileSystem, installRoot);
201
+ if (!installMetadata) {
202
+ return {
203
+ baseExists: true,
204
+ deploymentMode: "installed",
205
+ marker: null,
206
+ previousPort: null,
207
+ };
208
+ }
209
+
210
+ let markerContents;
211
+ const markerPath = join(installRoot, MANAGED_MARKER);
212
+ await assertRegularManagedMarker(
213
+ fileSystem,
214
+ markerPath,
215
+ "The local endpoint directory already exists without a Relmio managed marker. Nothing was overwritten.",
216
+ );
217
+ try {
218
+ markerContents = await fileSystem.readFile(markerPath, "utf8");
219
+ } catch (error) {
220
+ if (isMissing(error)) {
221
+ throw new Error(
222
+ "The local endpoint directory already exists without a Relmio managed marker. Nothing was overwritten.",
223
+ );
224
+ }
225
+ throw new Error("Relmio could not read its local managed marker.");
226
+ }
227
+
228
+ try {
229
+ const marker = JSON.parse(markerContents);
230
+ const installId = validateInstallId(marker?.installId);
231
+ const dockerHost = validateLocalDockerHost(marker?.dockerHost);
232
+ const projectName = `${PROJECTS[target].projectPrefix}-${installId}`;
233
+ if (
234
+ marker?.schemaVersion !== MARKER_SCHEMA_VERSION ||
235
+ marker?.target !== target ||
236
+ !Number.isInteger(marker?.port) ||
237
+ marker?.projectName !== projectName
238
+ ) {
239
+ throw new TypeError();
240
+ }
241
+ return {
242
+ baseExists: true,
243
+ deploymentMode: "updated",
244
+ marker: {
245
+ schemaVersion: MARKER_SCHEMA_VERSION,
246
+ target,
247
+ port: marker.port,
248
+ dockerHost,
249
+ installId,
250
+ projectName,
251
+ },
252
+ previousPort: marker.port,
253
+ };
254
+ } catch {
255
+ throw new Error("The local endpoint managed marker is invalid. Nothing was overwritten.");
256
+ }
257
+ }
258
+
259
+ async function initializeManagedBase({ fileSystem, relmioHome, baseExists }) {
260
+ if (baseExists) {
261
+ await fileSystem.chmod(relmioHome, 0o700);
262
+ return;
263
+ }
264
+ await fileSystem.mkdir(relmioHome, { mode: 0o700 });
265
+ await fileSystem.chmod(relmioHome, 0o700);
266
+ await writeManagedFile(
267
+ fileSystem,
268
+ join(relmioHome, ROOT_MARKER),
269
+ `${JSON.stringify({
270
+ schemaVersion: ROOT_MARKER_SCHEMA_VERSION,
271
+ kind: "relmio-local-root",
272
+ })}\n`,
273
+ 0o600,
274
+ );
275
+ }
276
+
277
+ async function ensurePrivateDirectory(fileSystem, path) {
278
+ const existing = await lstatIfExists(fileSystem, path);
279
+ if (existing) {
280
+ assertDirectoryMetadata(existing);
281
+ } else {
282
+ await fileSystem.mkdir(path, { mode: 0o700 });
283
+ }
284
+ await fileSystem.chmod(path, 0o700);
285
+ }
286
+
287
+ async function writeManagedFile(fileSystem, path, contents, mode) {
288
+ const existing = await lstatIfExists(fileSystem, path);
289
+ if (existing && (existing.isSymbolicLink() || !existing.isFile())) {
290
+ throw new Error("Relmio refuses to replace a non-file in its managed directory.");
291
+ }
292
+
293
+ const temporaryPath = `${path}.tmp-${randomUUID()}`;
294
+ try {
295
+ await fileSystem.writeFile(temporaryPath, contents, { flag: "wx", mode });
296
+ await fileSystem.chmod(temporaryPath, mode);
297
+ await fileSystem.rename(temporaryPath, path);
298
+ await fileSystem.chmod(path, mode);
299
+ } catch {
300
+ try {
301
+ await fileSystem.unlink(temporaryPath);
302
+ } catch {
303
+ // The temporary file may not have been created.
304
+ }
305
+ throw new Error("Relmio could not write its local managed files.");
306
+ }
307
+ }
308
+
309
+ export function isLoopbackPortAvailable(port) {
310
+ return new Promise((resolvePromise, rejectPromise) => {
311
+ const server = createServer();
312
+ server.unref();
313
+ server.once("error", (error) => {
314
+ if (error?.code === "EADDRINUSE" || error?.code === "EACCES") {
315
+ resolvePromise(false);
316
+ } else {
317
+ rejectPromise(new Error("Relmio could not check the local endpoint port."));
318
+ }
319
+ });
320
+ server.listen(port, "127.0.0.1", () => {
321
+ server.close((error) => {
322
+ if (error) {
323
+ rejectPromise(new Error("Relmio could not finish checking the local port."));
324
+ } else {
325
+ resolvePromise(true);
326
+ }
327
+ });
328
+ });
329
+ });
330
+ }
331
+
332
+ function validateVersion(value, label) {
333
+ const normalized = typeof value === "string" ? value.trim() : "";
334
+ if (!/^[A-Za-z0-9.+-]{1,64}$/u.test(normalized)) {
335
+ throw new Error(`${label} returned an invalid version.`);
336
+ }
337
+ return normalized;
338
+ }
339
+
340
+ function rejectDockerEnvironmentOverrides(env) {
341
+ for (const name of DOCKER_SELECTION_VARIABLES) {
342
+ if (typeof env[name] === "string" && env[name] !== "") {
343
+ throw new Error(
344
+ "Relmio local endpoints require the selected Docker context without Docker environment overrides.",
345
+ );
346
+ }
347
+ }
348
+ }
349
+
350
+ async function resolveLocalDockerHost({
351
+ runProcess,
352
+ cwd,
353
+ env,
354
+ platform,
355
+ }) {
356
+ assertSupportedPlatform(platform);
357
+ rejectDockerEnvironmentOverrides(env);
358
+ const context = await runProcess({
359
+ file: "docker",
360
+ args: [
361
+ "context",
362
+ "inspect",
363
+ "--format",
364
+ "{{json .Endpoints.docker.Host}}",
365
+ ],
366
+ cwd,
367
+ });
368
+ if (context.code !== 0) {
369
+ throw new Error("The selected Docker context could not be inspected.");
370
+ }
371
+ let candidate;
372
+ try {
373
+ candidate = JSON.parse(context.stdout.trim());
374
+ } catch {
375
+ throw new Error("The selected Docker context is not a local Docker daemon.");
376
+ }
377
+ try {
378
+ return validateLocalDockerHost(candidate, { platform });
379
+ } catch {
380
+ throw new Error("The selected Docker context is not a local Docker daemon.");
381
+ }
382
+ }
383
+
384
+ export async function getLocalDockerStatus({
385
+ runProcess = runLocalProcess,
386
+ cwd = process.cwd(),
387
+ env = process.env,
388
+ platform = process.platform,
389
+ } = {}) {
390
+ try {
391
+ const dockerHost = await resolveLocalDockerHost({
392
+ runProcess,
393
+ cwd,
394
+ env,
395
+ platform,
396
+ });
397
+ const docker = await runProcess({
398
+ file: "docker",
399
+ args: ["version", "--format", "{{.Server.Version}}"],
400
+ cwd,
401
+ dockerHost,
402
+ });
403
+ if (docker.code !== 0) {
404
+ throw new Error();
405
+ }
406
+ const compose = await runProcess({
407
+ file: "docker",
408
+ args: ["compose", "version", "--short"],
409
+ cwd,
410
+ dockerHost,
411
+ });
412
+ if (compose.code !== 0) {
413
+ throw new Error();
414
+ }
415
+ return {
416
+ dockerAvailable: true,
417
+ dockerVersion: validateVersion(docker.stdout, "Docker"),
418
+ composeVersion: validateVersion(compose.stdout, "Docker Compose"),
419
+ dockerHost,
420
+ };
421
+ } catch {
422
+ return {
423
+ dockerAvailable: false,
424
+ ...(platform === "win32" ? { unsupportedPlatform: true } : {}),
425
+ };
426
+ }
427
+ }
428
+
429
+ export async function restartLocalCodex(
430
+ { installDirectory },
431
+ dependencies = {},
432
+ ) {
433
+ const runProcess = dependencies.runProcess ?? runLocalProcess;
434
+ const attested = await attestLocalCodexInstallation(
435
+ { installDirectory },
436
+ dependencies,
437
+ );
438
+
439
+ await runOrThrow(runProcess, {
440
+ label: "Codex credential reload",
441
+ file: "docker",
442
+ args: createComposeArgs("codex-chatgpt", attested.projectName, [
443
+ "restart",
444
+ "--timeout",
445
+ "10",
446
+ "codex",
447
+ ]),
448
+ cwd: installDirectory,
449
+ dockerHost: attested.dockerHost,
450
+ });
451
+ await runOrThrow(runProcess, {
452
+ label: "Codex readiness wait",
453
+ file: "docker",
454
+ args: createComposeArgs("codex-chatgpt", attested.projectName, [
455
+ "up",
456
+ "-d",
457
+ "--wait",
458
+ "--wait-timeout",
459
+ "90",
460
+ "--no-deps",
461
+ "codex",
462
+ ]),
463
+ cwd: installDirectory,
464
+ dockerHost: attested.dockerHost,
465
+ });
466
+ return { restarted: true };
467
+ }
468
+
469
+ function createProjectName(target, installId) {
470
+ return `${PROJECTS[target].projectPrefix}-${validateInstallId(installId)}`;
471
+ }
472
+
473
+ function createComposeArgs(target, projectName, suffix) {
474
+ if (projectName !== createProjectName(target, projectName.slice(-32))) {
475
+ throw new TypeError("The local Docker project identity is invalid.");
476
+ }
477
+ return [
478
+ "compose",
479
+ "--project-name",
480
+ projectName,
481
+ "--file",
482
+ COMPOSE_FILENAME,
483
+ ...suffix,
484
+ ];
485
+ }
486
+
487
+ function createDeploymentSpecs({
488
+ target,
489
+ installRoot,
490
+ dockerHost,
491
+ projectName,
492
+ apiKey,
493
+ }) {
494
+ const project = PROJECTS[target];
495
+ const specs = [
496
+ {
497
+ label: "Local Compose validation",
498
+ file: "docker",
499
+ args: createComposeArgs(target, projectName, ["config", "--quiet"]),
500
+ cwd: installRoot,
501
+ dockerHost,
502
+ },
503
+ {
504
+ label: "Local image build",
505
+ file: "docker",
506
+ args: createComposeArgs(target, projectName, ["build", project.serviceName]),
507
+ cwd: installRoot,
508
+ dockerHost,
509
+ },
510
+ ];
511
+ if (target === "openai-api") {
512
+ specs.push({
513
+ label: "OpenAI Platform credential seed",
514
+ file: "docker",
515
+ args: createComposeArgs(target, projectName, [
516
+ "run",
517
+ "--rm",
518
+ "--no-deps",
519
+ "--no-build",
520
+ "-T",
521
+ "credential-seed",
522
+ ]),
523
+ cwd: installRoot,
524
+ dockerHost,
525
+ input: Buffer.from(validatePlatformApiKey(apiKey), "utf8"),
526
+ });
527
+ }
528
+ specs.push({
529
+ label: "Local endpoint start",
530
+ file: "docker",
531
+ args: createComposeArgs(target, projectName, [
532
+ "up",
533
+ "-d",
534
+ "--wait",
535
+ "--wait-timeout",
536
+ "90",
537
+ "--no-deps",
538
+ project.serviceName,
539
+ ]),
540
+ cwd: installRoot,
541
+ dockerHost,
542
+ });
543
+ return specs;
544
+ }
545
+
546
+ function createVerificationSpecs({ target, installRoot, dockerHost, projectName }) {
547
+ const project = PROJECTS[target];
548
+ return {
549
+ running: {
550
+ label: "Local endpoint status check",
551
+ file: "docker",
552
+ args: createComposeArgs(target, projectName, ["ps", "--status", "running", "--services"]),
553
+ cwd: installRoot,
554
+ dockerHost,
555
+ },
556
+ publication: {
557
+ label: "Local endpoint publication check",
558
+ file: "docker",
559
+ args: createComposeArgs(target, projectName, ["ps", "--format", "json", project.serviceName]),
560
+ cwd: installRoot,
561
+ dockerHost,
562
+ },
563
+ };
564
+ }
565
+
566
+ function createCleanupSpec({ target, installRoot, dockerHost, projectName }) {
567
+ const project = PROJECTS[target];
568
+ return {
569
+ label: "Unsafe local endpoint cleanup",
570
+ file: "docker",
571
+ args: createComposeArgs(target, projectName, [
572
+ "rm",
573
+ "--force",
574
+ "--stop",
575
+ project.serviceName,
576
+ ]),
577
+ cwd: installRoot,
578
+ dockerHost,
579
+ };
580
+ }
581
+
582
+ function createCleanupVerificationSpec({ target, installRoot, dockerHost, projectName }) {
583
+ const project = PROJECTS[target];
584
+ return {
585
+ label: "Local endpoint cleanup verification",
586
+ file: "docker",
587
+ args: createComposeArgs(target, projectName, [
588
+ "ps",
589
+ "--all",
590
+ "--services",
591
+ project.serviceName,
592
+ ]),
593
+ cwd: installRoot,
594
+ dockerHost,
595
+ };
596
+ }
597
+
598
+ function createOwnershipPreflightSpecs({
599
+ target,
600
+ installRoot,
601
+ dockerHost,
602
+ projectName,
603
+ }) {
604
+ const format = "{{json .}}";
605
+ const projectFilter = `label=com.docker.compose.project=${projectName}`;
606
+ return [
607
+ {
608
+ resource: "container",
609
+ label: "Local container ownership check",
610
+ file: "docker",
611
+ args: ["ps", "--all", "--filter", projectFilter, "--format", format],
612
+ cwd: installRoot,
613
+ dockerHost,
614
+ },
615
+ {
616
+ resource: "network",
617
+ label: "Local network ownership check",
618
+ file: "docker",
619
+ args: ["network", "ls", "--filter", projectFilter, "--format", format],
620
+ cwd: installRoot,
621
+ dockerHost,
622
+ },
623
+ {
624
+ resource: "volume",
625
+ label: "Local volume ownership check",
626
+ file: "docker",
627
+ args: ["volume", "ls", "--filter", projectFilter, "--format", format],
628
+ cwd: installRoot,
629
+ dockerHost,
630
+ },
631
+ ];
632
+ }
633
+
634
+ function parseDockerLabelSet(value) {
635
+ if (typeof value !== "string" || value.length > 16 * 1024) {
636
+ throw new Error("The local Docker ownership metadata is invalid.");
637
+ }
638
+ return new Set(value.split(",").filter(Boolean));
639
+ }
640
+
641
+ function validateOwnershipOutput(
642
+ output,
643
+ { target, installId, projectName, resource },
644
+ ) {
645
+ const rows = output.trim() === "" ? [] : output.trim().split("\n");
646
+ for (const row of rows) {
647
+ let parsed;
648
+ try {
649
+ parsed = JSON.parse(row);
650
+ } catch {
651
+ throw new Error("The local Docker ownership metadata is invalid.");
652
+ }
653
+ const labels = parseDockerLabelSet(parsed?.Labels);
654
+ for (const expected of [
655
+ `com.docker.compose.project=${projectName}`,
656
+ "io.relmio.managed=true",
657
+ `io.relmio.target=${target}`,
658
+ `io.relmio.install=${installId}`,
659
+ ]) {
660
+ if (!labels.has(expected)) {
661
+ throw new Error(
662
+ "A Docker resource already uses this Relmio project identity without matching ownership. Nothing was changed.",
663
+ );
664
+ }
665
+ }
666
+ if (
667
+ resource === "container" &&
668
+ !labels.has(`com.docker.compose.service=${PROJECTS[target].serviceName}`)
669
+ ) {
670
+ throw new Error(
671
+ "A Docker resource already uses this Relmio project identity without matching ownership. Nothing was changed.",
672
+ );
673
+ }
674
+ }
675
+ }
676
+
677
+ async function attestDockerOwnership({
678
+ target,
679
+ installRoot,
680
+ dockerHost,
681
+ installId,
682
+ projectName,
683
+ runProcess,
684
+ }) {
685
+ for (const spec of createOwnershipPreflightSpecs({
686
+ target,
687
+ installRoot,
688
+ dockerHost,
689
+ projectName,
690
+ })) {
691
+ const result = await runOrThrow(runProcess, spec);
692
+ validateOwnershipOutput(result.stdout, {
693
+ target,
694
+ installId,
695
+ projectName,
696
+ resource: spec.resource,
697
+ });
698
+ }
699
+ }
700
+
701
+ async function runOrThrow(runProcess, spec) {
702
+ const result = await runProcess({
703
+ file: spec.file,
704
+ args: spec.args,
705
+ cwd: spec.cwd,
706
+ ...(spec.dockerHost ? { dockerHost: spec.dockerHost } : {}),
707
+ ...(spec.input !== undefined ? { input: spec.input } : {}),
708
+ });
709
+ if (result.code !== 0) {
710
+ throw new Error(`${spec.label} failed.`);
711
+ }
712
+ return result;
713
+ }
714
+
715
+ function validatePublishedEndpoint(output, { target, port }) {
716
+ let services;
717
+ try {
718
+ const parsed = JSON.parse(output);
719
+ services = Array.isArray(parsed) ? parsed : [parsed];
720
+ } catch {
721
+ throw new Error("The local endpoint publication metadata is invalid.");
722
+ }
723
+ if (services.length !== 1 || !Array.isArray(services[0]?.Publishers)) {
724
+ throw new Error("The local endpoint publication check failed closed.");
725
+ }
726
+ const publishers = services[0].Publishers;
727
+ const expected = PROJECTS[target];
728
+ if (
729
+ publishers.length !== 1 ||
730
+ publishers[0]?.URL !== "127.0.0.1" ||
731
+ publishers[0]?.PublishedPort !== port ||
732
+ publishers[0]?.TargetPort !== expected.containerPort ||
733
+ publishers[0]?.Protocol !== "tcp"
734
+ ) {
735
+ throw new Error(
736
+ "The local endpoint publication is not the exact planned loopback binding.",
737
+ );
738
+ }
739
+ }
740
+
741
+ function parseModelIds(value) {
742
+ if (!Array.isArray(value?.data)) {
743
+ throw new Error("The OpenAI Platform model response could not be verified.");
744
+ }
745
+ const models = value.data
746
+ .map((entry) => entry?.id)
747
+ .filter(
748
+ (id) =>
749
+ typeof id === "string" &&
750
+ id.length > 0 &&
751
+ id.length <= 128 &&
752
+ /^[A-Za-z0-9_.:-]+$/u.test(id),
753
+ );
754
+ if (models.length === 0) {
755
+ throw new Error("The OpenAI Platform model response could not be verified.");
756
+ }
757
+ return models;
758
+ }
759
+
760
+ async function verifyHttpEndpoint({ plan, clientCredential, fetchImpl }) {
761
+ const healthPath = plan.target === "openai-api" ? "/health" : "/readyz";
762
+ const httpEndpoint = `http://127.0.0.1:${plan.port}${healthPath}`;
763
+ let health;
764
+ try {
765
+ health = await fetchImpl(httpEndpoint, {
766
+ method: "GET",
767
+ signal: AbortSignal.timeout(10_000),
768
+ });
769
+ } catch {
770
+ throw new Error("The local endpoint did not answer its readiness check.");
771
+ }
772
+ if (!health.ok) {
773
+ throw new Error("The local endpoint did not pass its readiness check.");
774
+ }
775
+
776
+ if (plan.target !== "openai-api") {
777
+ return [];
778
+ }
779
+
780
+ let response;
781
+ try {
782
+ response = await fetchImpl(`http://127.0.0.1:${plan.port}/v1/models`, {
783
+ method: "GET",
784
+ headers: { Authorization: `Bearer ${clientCredential}` },
785
+ signal: AbortSignal.timeout(15_000),
786
+ });
787
+ } catch {
788
+ throw new Error("The OpenAI Platform credential could not be verified.");
789
+ }
790
+ if (!response.ok) {
791
+ throw new Error("The OpenAI Platform credential could not be verified.");
792
+ }
793
+ try {
794
+ return parseModelIds(await response.json());
795
+ } catch (error) {
796
+ if (error?.message?.includes("model response")) {
797
+ throw error;
798
+ }
799
+ throw new Error("The OpenAI Platform model response could not be verified.");
800
+ }
801
+ }
802
+
803
+ async function defaultReadGatewaySource() {
804
+ return defaultFileSystem.readFile(
805
+ new URL("../gateway/openai.js", import.meta.url),
806
+ "utf8",
807
+ );
808
+ }
809
+
810
+ export async function attestLocalCodexInstallation(
811
+ { installDirectory },
812
+ {
813
+ fileSystem = defaultFileSystem,
814
+ runProcess = runLocalProcess,
815
+ platform = process.platform,
816
+ } = {},
817
+ ) {
818
+ assertSupportedPlatform(platform);
819
+ const safeDirectory = validateInstallDirectory(
820
+ installDirectory,
821
+ "codex-chatgpt",
822
+ );
823
+ const relmioHome = resolve(safeDirectory, "..", "..");
824
+ const managed = await inspectManagedRoot({
825
+ fileSystem,
826
+ relmioHome,
827
+ installRoot: safeDirectory,
828
+ target: "codex-chatgpt",
829
+ });
830
+ if (managed.deploymentMode !== "updated" || !managed.marker) {
831
+ throw new Error("Install the local Codex endpoint before signing in.");
832
+ }
833
+ await attestDockerOwnership({
834
+ target: "codex-chatgpt",
835
+ installRoot: safeDirectory,
836
+ dockerHost: managed.marker.dockerHost,
837
+ installId: managed.marker.installId,
838
+ projectName: managed.marker.projectName,
839
+ runProcess,
840
+ });
841
+ const verification = createVerificationSpecs({
842
+ target: "codex-chatgpt",
843
+ installRoot: safeDirectory,
844
+ dockerHost: managed.marker.dockerHost,
845
+ projectName: managed.marker.projectName,
846
+ });
847
+ const running = await runOrThrow(runProcess, verification.running);
848
+ if (!running.stdout.split(/\s+/u).includes("codex")) {
849
+ throw new Error("The managed local Codex endpoint is not running.");
850
+ }
851
+ const publication = await runOrThrow(runProcess, verification.publication);
852
+ validatePublishedEndpoint(publication.stdout, {
853
+ target: "codex-chatgpt",
854
+ port: managed.marker.port,
855
+ });
856
+ return {
857
+ dockerHost: managed.marker.dockerHost,
858
+ projectName: managed.marker.projectName,
859
+ };
860
+ }
861
+
862
+ export async function installLocalEndpoint(
863
+ { plan, apiKey, confirmed },
864
+ {
865
+ fileSystem = defaultFileSystem,
866
+ env = process.env,
867
+ homeDirectory = homedir(),
868
+ runProcess = runLocalProcess,
869
+ randomBytes = createRandomBytes,
870
+ isPortAvailable = isLoopbackPortAvailable,
871
+ readGatewaySource = defaultReadGatewaySource,
872
+ fetchImpl = fetch,
873
+ platform = process.platform,
874
+ } = {},
875
+ ) {
876
+ if (confirmed !== true) {
877
+ throw new Error("Confirm the reviewed local endpoint plan before installing.");
878
+ }
879
+ assertSupportedPlatform(platform);
880
+ rejectDockerEnvironmentOverrides(env);
881
+
882
+ const normalizedPlan = createLocalDeploymentPlan({
883
+ target: plan?.target,
884
+ port: plan?.port,
885
+ allowedOrigins: plan?.allowedOrigins,
886
+ });
887
+ const safeApiKey =
888
+ normalizedPlan.target === "openai-api"
889
+ ? validatePlatformApiKey(apiKey)
890
+ : null;
891
+ const installRoot = await resolveLocalInstallRoot({
892
+ target: normalizedPlan.target,
893
+ env,
894
+ homeDirectory,
895
+ fileSystem,
896
+ platform,
897
+ });
898
+ const relmioHome = resolve(installRoot, "..", "..");
899
+ const managed = await inspectManagedRoot({
900
+ fileSystem,
901
+ relmioHome,
902
+ installRoot,
903
+ target: normalizedPlan.target,
904
+ });
905
+ const dockerHost = managed.marker?.dockerHost ??
906
+ (await resolveLocalDockerHost({
907
+ runProcess,
908
+ cwd: dirname(relmioHome),
909
+ env,
910
+ platform,
911
+ }));
912
+ const installIdBytes = managed.marker ? null : randomBytes(32);
913
+ if (
914
+ installIdBytes !== null &&
915
+ (!Buffer.isBuffer(installIdBytes) || installIdBytes.length !== 32)
916
+ ) {
917
+ throw new Error("Relmio could not generate a strong installation identity.");
918
+ }
919
+ const installId = managed.marker?.installId ??
920
+ installIdBytes.subarray(0, 16).toString("hex");
921
+ validateInstallId(installId);
922
+ const projectName = createProjectName(normalizedPlan.target, installId);
923
+ await attestDockerOwnership({
924
+ target: normalizedPlan.target,
925
+ installRoot: managed.marker ? installRoot : dirname(relmioHome),
926
+ dockerHost,
927
+ installId,
928
+ projectName,
929
+ runProcess,
930
+ });
931
+
932
+ if (
933
+ managed.previousPort !== normalizedPlan.port &&
934
+ !(await isPortAvailable(normalizedPlan.port))
935
+ ) {
936
+ throw new Error("The selected local endpoint port is already in use.");
937
+ }
938
+
939
+ const capabilityBytes = randomBytes(32);
940
+ if (!Buffer.isBuffer(capabilityBytes) || capabilityBytes.length !== 32) {
941
+ throw new Error("Relmio could not generate a strong local capability.");
942
+ }
943
+ const clientCredential = capabilityBytes.toString("base64url");
944
+ const tokenSha256 = createHash("sha256")
945
+ .update(clientCredential)
946
+ .digest("hex");
947
+
948
+ await initializeManagedBase({
949
+ fileSystem,
950
+ relmioHome,
951
+ baseExists: managed.baseExists,
952
+ });
953
+ await ensurePrivateDirectory(fileSystem, join(relmioHome, "local"));
954
+ await ensurePrivateDirectory(fileSystem, installRoot);
955
+
956
+ let dockerfile;
957
+ let composeFile;
958
+ if (normalizedPlan.target === "openai-api") {
959
+ const gatewaySource = await readGatewaySource();
960
+ if (
961
+ typeof gatewaySource !== "string" ||
962
+ gatewaySource.length === 0 ||
963
+ gatewaySource.length > 512 * 1024
964
+ ) {
965
+ throw new Error("The packaged local gateway runtime is invalid.");
966
+ }
967
+ dockerfile = createOpenAiGatewayDockerfile();
968
+ composeFile = createOpenAiGatewayComposeFile({
969
+ port: normalizedPlan.port,
970
+ tokenSha256,
971
+ allowedOrigins: normalizedPlan.allowedOrigins,
972
+ installId,
973
+ });
974
+ await writeManagedFile(
975
+ fileSystem,
976
+ join(installRoot, "gateway.mjs"),
977
+ gatewaySource,
978
+ 0o600,
979
+ );
980
+ } else {
981
+ dockerfile = createCodexDockerfile();
982
+ composeFile = createCodexComposeFile({
983
+ port: normalizedPlan.port,
984
+ tokenSha256,
985
+ installId,
986
+ });
987
+ await writeManagedFile(
988
+ fileSystem,
989
+ join(installRoot, "config.toml"),
990
+ createCodexConfig(),
991
+ 0o600,
992
+ );
993
+ await writeManagedFile(
994
+ fileSystem,
995
+ join(installRoot, "requirements.toml"),
996
+ createCodexRequirements(),
997
+ 0o600,
998
+ );
999
+ }
1000
+
1001
+ await writeManagedFile(
1002
+ fileSystem,
1003
+ join(installRoot, "Dockerfile"),
1004
+ dockerfile,
1005
+ 0o600,
1006
+ );
1007
+ await writeManagedFile(
1008
+ fileSystem,
1009
+ join(installRoot, ".dockerignore"),
1010
+ createLocalDockerignore(normalizedPlan.target),
1011
+ 0o600,
1012
+ );
1013
+ await writeManagedFile(
1014
+ fileSystem,
1015
+ join(installRoot, COMPOSE_FILENAME),
1016
+ composeFile,
1017
+ 0o600,
1018
+ );
1019
+ await writeManagedFile(
1020
+ fileSystem,
1021
+ join(installRoot, MANAGED_MARKER),
1022
+ `${JSON.stringify({
1023
+ schemaVersion: MARKER_SCHEMA_VERSION,
1024
+ target: normalizedPlan.target,
1025
+ port: normalizedPlan.port,
1026
+ dockerHost,
1027
+ installId,
1028
+ projectName,
1029
+ })}\n`,
1030
+ 0o600,
1031
+ );
1032
+
1033
+ let deploymentStarted = false;
1034
+ let models;
1035
+ try {
1036
+ for (const spec of createDeploymentSpecs({
1037
+ target: normalizedPlan.target,
1038
+ installRoot,
1039
+ dockerHost,
1040
+ projectName,
1041
+ apiKey: safeApiKey,
1042
+ })) {
1043
+ if (spec.args.includes("up")) {
1044
+ deploymentStarted = true;
1045
+ }
1046
+ await runOrThrow(runProcess, spec);
1047
+ }
1048
+ const verification = createVerificationSpecs({
1049
+ target: normalizedPlan.target,
1050
+ installRoot,
1051
+ dockerHost,
1052
+ projectName,
1053
+ });
1054
+ const running = await runOrThrow(runProcess, verification.running);
1055
+ if (
1056
+ !running.stdout
1057
+ .split(/\s+/u)
1058
+ .includes(PROJECTS[normalizedPlan.target].serviceName)
1059
+ ) {
1060
+ throw new Error("The local endpoint did not reach the running state.");
1061
+ }
1062
+ const publication = await runOrThrow(runProcess, verification.publication);
1063
+ validatePublishedEndpoint(publication.stdout, {
1064
+ target: normalizedPlan.target,
1065
+ port: normalizedPlan.port,
1066
+ });
1067
+ models = await verifyHttpEndpoint({
1068
+ plan: normalizedPlan,
1069
+ clientCredential,
1070
+ fetchImpl,
1071
+ });
1072
+ } catch (error) {
1073
+ if (deploymentStarted) {
1074
+ let cleanupConfirmed = false;
1075
+ try {
1076
+ await runOrThrow(
1077
+ runProcess,
1078
+ createCleanupSpec({
1079
+ target: normalizedPlan.target,
1080
+ installRoot,
1081
+ dockerHost,
1082
+ projectName,
1083
+ }),
1084
+ );
1085
+ const remaining = await runOrThrow(
1086
+ runProcess,
1087
+ createCleanupVerificationSpec({
1088
+ target: normalizedPlan.target,
1089
+ installRoot,
1090
+ dockerHost,
1091
+ projectName,
1092
+ }),
1093
+ );
1094
+ cleanupConfirmed = !remaining.stdout
1095
+ .split(/\s+/u)
1096
+ .includes(PROJECTS[normalizedPlan.target].serviceName);
1097
+ } catch {
1098
+ // The caller receives a stronger fail-closed error below.
1099
+ }
1100
+ if (!cleanupConfirmed) {
1101
+ throw new Error(
1102
+ "Relmio could not confirm that the failed local endpoint was stopped. Inspect the Relmio Docker project before retrying.",
1103
+ );
1104
+ }
1105
+ }
1106
+ throw error;
1107
+ }
1108
+
1109
+ return {
1110
+ target: normalizedPlan.target,
1111
+ endpoint: normalizedPlan.endpoint,
1112
+ protocol: normalizedPlan.protocol,
1113
+ clientCredential,
1114
+ credentialShownOnce: true,
1115
+ models,
1116
+ deploymentMode: managed.deploymentMode,
1117
+ experimental: normalizedPlan.experimental,
1118
+ browserClients: normalizedPlan.browserClients,
1119
+ };
1120
+ }