@geminixiang/mikan 1.0.0-beta.21 → 1.0.0-beta.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +55 -0
- package/README.md +12 -8
- package/dist/adapters/discord/bot.d.ts.map +1 -1
- package/dist/adapters/discord/bot.js +13 -14
- package/dist/adapters/discord/bot.js.map +1 -1
- package/dist/adapters/intake.d.ts.map +1 -1
- package/dist/adapters/intake.js +5 -0
- package/dist/adapters/intake.js.map +1 -1
- package/dist/adapters/slack/bot.d.ts.map +1 -1
- package/dist/adapters/slack/bot.js +13 -2
- package/dist/adapters/slack/bot.js.map +1 -1
- package/dist/adapters/slack/types.d.ts +2 -0
- package/dist/adapters/slack/types.d.ts.map +1 -1
- package/dist/adapters/slack/types.js.map +1 -1
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +34 -24
- package/dist/agent.js.map +1 -1
- package/dist/cli/boot.d.ts +1 -1
- package/dist/cli/boot.d.ts.map +1 -1
- package/dist/cli/boot.js +7 -15
- package/dist/cli/boot.js.map +1 -1
- package/dist/commands/login.js +2 -2
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/sandbox.d.ts.map +1 -1
- package/dist/commands/sandbox.js +4 -7
- package/dist/commands/sandbox.js.map +1 -1
- package/dist/commands/text.js +1 -1
- package/dist/commands/text.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +12 -39
- package/dist/config.js.map +1 -1
- package/dist/execution-resolver.d.ts +6 -8
- package/dist/execution-resolver.d.ts.map +1 -1
- package/dist/execution-resolver.js +73 -107
- package/dist/execution-resolver.js.map +1 -1
- package/dist/harness/index.d.ts +1 -1
- package/dist/harness/index.d.ts.map +1 -1
- package/dist/harness/index.js.map +1 -1
- package/dist/harness/runner.d.ts +9 -6
- package/dist/harness/runner.d.ts.map +1 -1
- package/dist/harness/runner.js +42 -20
- package/dist/harness/runner.js.map +1 -1
- package/dist/harness/subagent-runner.d.ts +14 -13
- package/dist/harness/subagent-runner.d.ts.map +1 -1
- package/dist/harness/subagent-runner.js +116 -24
- package/dist/harness/subagent-runner.js.map +1 -1
- package/dist/harness/types.d.ts +14 -0
- package/dist/harness/types.d.ts.map +1 -1
- package/dist/harness/types.js.map +1 -1
- package/dist/harness/usage.d.ts +6 -0
- package/dist/harness/usage.d.ts.map +1 -0
- package/dist/harness/usage.js +32 -0
- package/dist/harness/usage.js.map +1 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/main.js +19 -87
- package/dist/main.js.map +1 -1
- package/dist/runtime/conversation-runtime.d.ts.map +1 -1
- package/dist/runtime/conversation-runtime.js +23 -79
- package/dist/runtime/conversation-runtime.js.map +1 -1
- package/dist/runtime/session-lifecycle.d.ts +19 -0
- package/dist/runtime/session-lifecycle.d.ts.map +1 -0
- package/dist/runtime/session-lifecycle.js +76 -0
- package/dist/runtime/session-lifecycle.js.map +1 -0
- package/dist/runtime/types.d.ts +5 -0
- package/dist/runtime/types.d.ts.map +1 -1
- package/dist/runtime/types.js.map +1 -1
- package/dist/sandbox/agent-sandbox.d.ts +18 -0
- package/dist/sandbox/agent-sandbox.d.ts.map +1 -0
- package/dist/sandbox/agent-sandbox.js +234 -0
- package/dist/sandbox/agent-sandbox.js.map +1 -0
- package/dist/sandbox/cloudflare.d.ts.map +1 -1
- package/dist/sandbox/cloudflare.js +1 -0
- package/dist/sandbox/cloudflare.js.map +1 -1
- package/dist/sandbox/container.d.ts.map +1 -1
- package/dist/sandbox/container.js +1 -0
- package/dist/sandbox/container.js.map +1 -1
- package/dist/sandbox/host.d.ts.map +1 -1
- package/dist/sandbox/host.js +1 -0
- package/dist/sandbox/host.js.map +1 -1
- package/dist/sandbox/identity.d.ts +11 -16
- package/dist/sandbox/identity.d.ts.map +1 -1
- package/dist/sandbox/identity.js +32 -26
- package/dist/sandbox/identity.js.map +1 -1
- package/dist/sandbox/image.d.ts.map +1 -1
- package/dist/sandbox/image.js +1 -0
- package/dist/sandbox/image.js.map +1 -1
- package/dist/sandbox/index.d.ts +7 -7
- package/dist/sandbox/index.d.ts.map +1 -1
- package/dist/sandbox/index.js +13 -9
- package/dist/sandbox/index.js.map +1 -1
- package/dist/sandbox/types.d.ts +17 -16
- package/dist/sandbox/types.d.ts.map +1 -1
- package/dist/sandbox/types.js.map +1 -1
- package/dist/sessions/session-key.d.ts +9 -0
- package/dist/sessions/session-key.d.ts.map +1 -1
- package/dist/sessions/session-key.js +50 -2
- package/dist/sessions/session-key.js.map +1 -1
- package/dist/sessions/store.d.ts.map +1 -1
- package/dist/sessions/store.js +36 -8
- package/dist/sessions/store.js.map +1 -1
- package/dist/tools/sandbox.d.ts.map +1 -1
- package/dist/tools/sandbox.js +4 -5
- package/dist/tools/sandbox.js.map +1 -1
- package/dist/tools/subagent-slots.d.ts +3 -3
- package/dist/tools/subagent-slots.d.ts.map +1 -1
- package/dist/tools/subagent-slots.js +25 -10
- package/dist/tools/subagent-slots.js.map +1 -1
- package/dist/tools/subagent.d.ts +13 -2
- package/dist/tools/subagent.d.ts.map +1 -1
- package/dist/tools/subagent.js +16 -16
- package/dist/tools/subagent.js.map +1 -1
- package/dist/types.d.ts +14 -63
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/vault/injection.d.ts +4 -14
- package/dist/vault/injection.d.ts.map +1 -1
- package/dist/vault/injection.js +5 -2
- package/dist/vault/injection.js.map +1 -1
- package/dist/vault/policy.d.ts +3 -3
- package/dist/vault/policy.d.ts.map +1 -1
- package/dist/vault/policy.js +6 -4
- package/dist/vault/policy.js.map +1 -1
- package/dist/vault/types.d.ts +4 -0
- package/dist/vault/types.d.ts.map +1 -1
- package/dist/vault/types.js.map +1 -1
- package/dist/web/admin/portal.js +2 -2
- package/dist/web/admin/portal.js.map +1 -1
- package/dist/workspace-projection/index.d.ts +5 -0
- package/dist/workspace-projection/index.d.ts.map +1 -0
- package/dist/workspace-projection/index.js +71 -0
- package/dist/workspace-projection/index.js.map +1 -0
- package/dist/workspace-projection/types.d.ts +7 -0
- package/dist/workspace-projection/types.d.ts.map +1 -0
- package/dist/workspace-projection/types.js +2 -0
- package/dist/workspace-projection/types.js.map +1 -0
- package/package.json +3 -3
- package/dist/sandbox/firecracker.d.ts +0 -18
- package/dist/sandbox/firecracker.d.ts.map +0 -1
- package/dist/sandbox/firecracker.js +0 -221
- package/dist/sandbox/firecracker.js.map +0 -1
- package/dist/sandbox/gondolin-bootstrap.d.ts +0 -26
- package/dist/sandbox/gondolin-bootstrap.d.ts.map +0 -1
- package/dist/sandbox/gondolin-bootstrap.js +0 -27
- package/dist/sandbox/gondolin-bootstrap.js.map +0 -1
- package/dist/sandbox/gondolin-contract.d.ts +0 -125
- package/dist/sandbox/gondolin-contract.d.ts.map +0 -1
- package/dist/sandbox/gondolin-contract.js +0 -155
- package/dist/sandbox/gondolin-contract.js.map +0 -1
- package/dist/sandbox/gondolin-fleet.d.ts +0 -104
- package/dist/sandbox/gondolin-fleet.d.ts.map +0 -1
- package/dist/sandbox/gondolin-fleet.js +0 -330
- package/dist/sandbox/gondolin-fleet.js.map +0 -1
- package/dist/sandbox/gondolin-gateway.d.ts +0 -86
- package/dist/sandbox/gondolin-gateway.d.ts.map +0 -1
- package/dist/sandbox/gondolin-gateway.js +0 -407
- package/dist/sandbox/gondolin-gateway.js.map +0 -1
- package/dist/sandbox/gondolin-inventory.d.ts +0 -75
- package/dist/sandbox/gondolin-inventory.d.ts.map +0 -1
- package/dist/sandbox/gondolin-inventory.js +0 -257
- package/dist/sandbox/gondolin-inventory.js.map +0 -1
- package/dist/sandbox/gondolin-join.d.ts +0 -62
- package/dist/sandbox/gondolin-join.d.ts.map +0 -1
- package/dist/sandbox/gondolin-join.js +0 -261
- package/dist/sandbox/gondolin-join.js.map +0 -1
- package/dist/sandbox/gondolin-nfs-advisory.d.ts +0 -15
- package/dist/sandbox/gondolin-nfs-advisory.d.ts.map +0 -1
- package/dist/sandbox/gondolin-nfs-advisory.js +0 -60
- package/dist/sandbox/gondolin-nfs-advisory.js.map +0 -1
- package/dist/sandbox/gondolin-placement.d.ts +0 -30
- package/dist/sandbox/gondolin-placement.d.ts.map +0 -1
- package/dist/sandbox/gondolin-placement.js +0 -77
- package/dist/sandbox/gondolin-placement.js.map +0 -1
- package/dist/sandbox/gondolin-recovery.d.ts +0 -33
- package/dist/sandbox/gondolin-recovery.d.ts.map +0 -1
- package/dist/sandbox/gondolin-recovery.js +0 -39
- package/dist/sandbox/gondolin-recovery.js.map +0 -1
- package/dist/sandbox/gondolin-remote.d.ts +0 -70
- package/dist/sandbox/gondolin-remote.d.ts.map +0 -1
- package/dist/sandbox/gondolin-remote.js +0 -325
- package/dist/sandbox/gondolin-remote.js.map +0 -1
- package/dist/sandbox/gondolin-worker-client.d.ts +0 -135
- package/dist/sandbox/gondolin-worker-client.d.ts.map +0 -1
- package/dist/sandbox/gondolin-worker-client.js +0 -323
- package/dist/sandbox/gondolin-worker-client.js.map +0 -1
- package/dist/sandbox/gondolin-worker-main.d.ts +0 -2
- package/dist/sandbox/gondolin-worker-main.d.ts.map +0 -1
- package/dist/sandbox/gondolin-worker-main.js +0 -22
- package/dist/sandbox/gondolin-worker-main.js.map +0 -1
- package/dist/sandbox/gondolin-worker.d.ts +0 -17
- package/dist/sandbox/gondolin-worker.d.ts.map +0 -1
- package/dist/sandbox/gondolin-worker.js +0 -201
- package/dist/sandbox/gondolin-worker.js.map +0 -1
- package/dist/sandbox/gondolin.d.ts +0 -53
- package/dist/sandbox/gondolin.d.ts.map +0 -1
- package/dist/sandbox/gondolin.js +0 -429
- package/dist/sandbox/gondolin.js.map +0 -1
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Durable record of one Gondolin runtime. Written beside the state dir (never
|
|
3
|
-
* inside the workspace) by whichever process owns the VM — the worker process
|
|
4
|
-
* hosting it — so a crash that skips every exit hook still leaves enough
|
|
5
|
-
* behind to adopt the runtime back or stop its orphaned VM runner.
|
|
6
|
-
*/
|
|
7
|
-
export type { GondolinRuntimeRecord } from "./gondolin-contract.js";
|
|
8
|
-
import { type GondolinRuntimeRecord } from "./gondolin-contract.js";
|
|
9
|
-
interface GondolinInventoryOverrides {
|
|
10
|
-
execFile?: (cmd: string, args: string[]) => Promise<string>;
|
|
11
|
-
kill?: (pid: number, signal: NodeJS.Signals | 0) => void;
|
|
12
|
-
gcSessions?: () => Promise<number>;
|
|
13
|
-
killWaitMs?: number;
|
|
14
|
-
killPollIntervalMs?: number;
|
|
15
|
-
}
|
|
16
|
-
declare class GondolinRuntimeInventory {
|
|
17
|
-
private dir?;
|
|
18
|
-
private execFileImpl;
|
|
19
|
-
private killImpl;
|
|
20
|
-
private gcSessionsImpl;
|
|
21
|
-
private killWaitMs;
|
|
22
|
-
private killPollIntervalMs;
|
|
23
|
-
private readonly liveRecords;
|
|
24
|
-
configure(dir?: string, overrides?: GondolinInventoryOverrides): void;
|
|
25
|
-
record(entry: {
|
|
26
|
-
sessionId: string;
|
|
27
|
-
instanceId: string;
|
|
28
|
-
runnerPid: number | null;
|
|
29
|
-
socketPath?: string;
|
|
30
|
-
fingerprint?: string;
|
|
31
|
-
}): void;
|
|
32
|
-
/** Re-persist a runtime whose runner pid changed (late boot, runner restart). */
|
|
33
|
-
refresh(sessionId: string, runnerPid: number | null): void;
|
|
34
|
-
release(sessionId: string): void;
|
|
35
|
-
/**
|
|
36
|
-
* Newest worker-owned record for a session key whose owner is still alive —
|
|
37
|
-
* a candidate for adoption instead of spawning a fresh worker. Records left
|
|
38
|
-
* by dead owners are reaped on the spot.
|
|
39
|
-
*/
|
|
40
|
-
findAdoptable(instanceId: string): Promise<GondolinRuntimeRecord | undefined>;
|
|
41
|
-
/** Reap one session's record if its owner is gone (e.g. after force-killing a worker). */
|
|
42
|
-
reapSession(sessionId: string): Promise<void>;
|
|
43
|
-
/** Worker-owned records with live owners (runtimes surviving from a previous mikan). */
|
|
44
|
-
listWorkerRecords(): GondolinRuntimeRecord[];
|
|
45
|
-
/**
|
|
46
|
-
* mikan liveness signal for workers: a worker whose heartbeat goes stale
|
|
47
|
-
* knows every mikan that could adopt it is gone and shuts itself down.
|
|
48
|
-
*/
|
|
49
|
-
touchHeartbeat(): void;
|
|
50
|
-
heartbeatPath(): string | undefined;
|
|
51
|
-
directory(): string | undefined;
|
|
52
|
-
/**
|
|
53
|
-
* Startup reconciliation: stop VM runners orphaned by a previous owner
|
|
54
|
-
* process that died without cleanup, drop stale records, and let Gondolin
|
|
55
|
-
* collect its own stale session registry entries. Runtimes whose owner is
|
|
56
|
-
* still alive (workers surviving a mikan restart) are left for adoption.
|
|
57
|
-
* Idempotent — a second run finds an empty inventory.
|
|
58
|
-
*/
|
|
59
|
-
reconcile(): Promise<void>;
|
|
60
|
-
/**
|
|
61
|
-
* Reap one dead-owner record: stop its orphaned runner if it is still the
|
|
62
|
-
* process we launched, then drop the record (kept only while the runner
|
|
63
|
-
* refuses to die, so the next reconcile retries).
|
|
64
|
-
*/
|
|
65
|
-
reapRecord(record: GondolinRuntimeRecord, path?: string): Promise<"killed" | "gone" | "reused" | "alive">;
|
|
66
|
-
private readRecords;
|
|
67
|
-
private stopOrphan;
|
|
68
|
-
private signal;
|
|
69
|
-
isPidAlive(pid: number): boolean;
|
|
70
|
-
private waitForExit;
|
|
71
|
-
private write;
|
|
72
|
-
private recordPath;
|
|
73
|
-
}
|
|
74
|
-
export declare const gondolinInventory: GondolinRuntimeInventory;
|
|
75
|
-
//# sourceMappingURL=gondolin-inventory.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"gondolin-inventory.d.ts","sourceRoot":"","sources":["../../src/sandbox/gondolin-inventory.ts"],"names":[],"mappings":"AAMA;;;;;GAKG;AAGH,YAAY,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AACpE,OAAO,EAA2B,KAAK,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAE7F,UAAU,0BAA0B;IAClC,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC5D,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,OAAO,GAAG,CAAC,KAAK,IAAI,CAAC;IACzD,UAAU,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IACnC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAOD,cAAM,wBAAwB;IAC5B,OAAO,CAAC,GAAG,CAAC,CAAS;IACrB,OAAO,CAAC,YAAY,CAAc;IAClC,OAAO,CAAC,QAAQ,CACY;IAC5B,OAAO,CAAC,cAAc,CAAqB;IAC3C,OAAO,CAAC,UAAU,CAAQ;IAC1B,OAAO,CAAC,kBAAkB,CAAO;IACjC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA4C;IAExE,SAAS,CAAC,GAAG,CAAC,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,0BAA0B,GAAG,IAAI,CAQpE;IAED,MAAM,CAAC,KAAK,EAAE;QACZ,SAAS,EAAE,MAAM,CAAC;QAClB,UAAU,EAAE,MAAM,CAAC;QACnB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;QACzB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,WAAW,CAAC,EAAE,MAAM,CAAC;KACtB,GAAG,IAAI,CASP;IAED,iFAAiF;IACjF,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAKzD;IAED,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAW/B;IAED;;;;OAIG;IACG,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,GAAG,SAAS,CAAC,CAWlF;IAED,0FAA0F;IACpF,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAMlD;IAED,wFAAwF;IACxF,iBAAiB,IAAI,qBAAqB,EAAE,CAI3C;IAED;;;OAGG;IACH,cAAc,IAAI,IAAI,CAWrB;IAED,aAAa,IAAI,MAAM,GAAG,SAAS,CAElC;IAED,SAAS,IAAI,MAAM,GAAG,SAAS,CAE9B;IAED;;;;;;OAMG;IACG,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC,CA8B/B;IAED;;;;OAIG;IACG,UAAU,CACd,MAAM,EAAE,qBAAqB,EAC7B,IAAI,SAAoC,GACvC,OAAO,CAAC,QAAQ,GAAG,MAAM,GAAG,QAAQ,GAAG,OAAO,CAAC,CAUjD;IAED,OAAO,CAAC,WAAW;YA4BL,UAAU;IA6BxB,OAAO,CAAC,MAAM;IAQd,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAO/B;YAEa,WAAW;IASzB,OAAO,CAAC,KAAK;IAab,OAAO,CAAC,UAAU;CAGnB;AAED,eAAO,MAAM,iBAAiB,0BAAiC,CAAC"}
|
|
@@ -1,257 +0,0 @@
|
|
|
1
|
-
import { readdirSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
import * as log from "../log.js";
|
|
4
|
-
import { ensureDirExists, readJsonFileIfExists } from "../utils/file-guards.js";
|
|
5
|
-
import { execSimple } from "./utils.js";
|
|
6
|
-
import { isGondolinRuntimeRecord } from "./gondolin-contract.js";
|
|
7
|
-
async function defaultGcSessions() {
|
|
8
|
-
const { gcSessions } = await import("@earendil-works/gondolin");
|
|
9
|
-
return gcSessions();
|
|
10
|
-
}
|
|
11
|
-
class GondolinRuntimeInventory {
|
|
12
|
-
constructor() {
|
|
13
|
-
this.execFileImpl = execSimple;
|
|
14
|
-
this.killImpl = (pid, signal) => process.kill(pid, signal);
|
|
15
|
-
this.gcSessionsImpl = defaultGcSessions;
|
|
16
|
-
this.killWaitMs = 2000;
|
|
17
|
-
this.killPollIntervalMs = 100;
|
|
18
|
-
this.liveRecords = new Map();
|
|
19
|
-
}
|
|
20
|
-
configure(dir, overrides) {
|
|
21
|
-
this.dir = dir;
|
|
22
|
-
this.execFileImpl = overrides?.execFile ?? execSimple;
|
|
23
|
-
this.killImpl = overrides?.kill ?? ((pid, signal) => process.kill(pid, signal));
|
|
24
|
-
this.gcSessionsImpl = overrides?.gcSessions ?? defaultGcSessions;
|
|
25
|
-
this.killWaitMs = overrides?.killWaitMs ?? 2000;
|
|
26
|
-
this.killPollIntervalMs = overrides?.killPollIntervalMs ?? 100;
|
|
27
|
-
this.liveRecords.clear();
|
|
28
|
-
}
|
|
29
|
-
record(entry) {
|
|
30
|
-
if (!this.dir)
|
|
31
|
-
return;
|
|
32
|
-
const record = {
|
|
33
|
-
...entry,
|
|
34
|
-
ownerPid: process.pid,
|
|
35
|
-
createdAt: new Date().toISOString(),
|
|
36
|
-
};
|
|
37
|
-
this.liveRecords.set(entry.sessionId, record);
|
|
38
|
-
this.write(record);
|
|
39
|
-
}
|
|
40
|
-
/** Re-persist a runtime whose runner pid changed (late boot, runner restart). */
|
|
41
|
-
refresh(sessionId, runnerPid) {
|
|
42
|
-
const record = this.liveRecords.get(sessionId);
|
|
43
|
-
if (!record || record.runnerPid === runnerPid)
|
|
44
|
-
return;
|
|
45
|
-
record.runnerPid = runnerPid;
|
|
46
|
-
this.write(record);
|
|
47
|
-
}
|
|
48
|
-
release(sessionId) {
|
|
49
|
-
this.liveRecords.delete(sessionId);
|
|
50
|
-
if (!this.dir)
|
|
51
|
-
return;
|
|
52
|
-
try {
|
|
53
|
-
rmSync(this.recordPath(sessionId), { force: true });
|
|
54
|
-
}
|
|
55
|
-
catch (err) {
|
|
56
|
-
log.logWarning(`Failed to release Gondolin runtime record '${sessionId}'`, err instanceof Error ? err.message : String(err));
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
/**
|
|
60
|
-
* Newest worker-owned record for a session key whose owner is still alive —
|
|
61
|
-
* a candidate for adoption instead of spawning a fresh worker. Records left
|
|
62
|
-
* by dead owners are reaped on the spot.
|
|
63
|
-
*/
|
|
64
|
-
async findAdoptable(instanceId) {
|
|
65
|
-
let newest;
|
|
66
|
-
for (const { record, path } of this.readRecords()) {
|
|
67
|
-
if (record.instanceId !== instanceId || !record.socketPath)
|
|
68
|
-
continue;
|
|
69
|
-
if (!this.isPidAlive(record.ownerPid)) {
|
|
70
|
-
await this.reapRecord(record, path);
|
|
71
|
-
continue;
|
|
72
|
-
}
|
|
73
|
-
if (!newest || record.createdAt > newest.createdAt)
|
|
74
|
-
newest = record;
|
|
75
|
-
}
|
|
76
|
-
return newest;
|
|
77
|
-
}
|
|
78
|
-
/** Reap one session's record if its owner is gone (e.g. after force-killing a worker). */
|
|
79
|
-
async reapSession(sessionId) {
|
|
80
|
-
for (const { record, path } of this.readRecords()) {
|
|
81
|
-
if (record.sessionId !== sessionId)
|
|
82
|
-
continue;
|
|
83
|
-
if (!this.isPidAlive(record.ownerPid))
|
|
84
|
-
await this.reapRecord(record, path);
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
/** Worker-owned records with live owners (runtimes surviving from a previous mikan). */
|
|
89
|
-
listWorkerRecords() {
|
|
90
|
-
return this.readRecords()
|
|
91
|
-
.map(({ record }) => record)
|
|
92
|
-
.filter((record) => record.socketPath !== undefined && this.isPidAlive(record.ownerPid));
|
|
93
|
-
}
|
|
94
|
-
/**
|
|
95
|
-
* mikan liveness signal for workers: a worker whose heartbeat goes stale
|
|
96
|
-
* knows every mikan that could adopt it is gone and shuts itself down.
|
|
97
|
-
*/
|
|
98
|
-
touchHeartbeat() {
|
|
99
|
-
if (!this.dir)
|
|
100
|
-
return;
|
|
101
|
-
try {
|
|
102
|
-
ensureDirExists(this.dir);
|
|
103
|
-
writeFileSync(this.heartbeatPath(), new Date().toISOString() + "\n");
|
|
104
|
-
}
|
|
105
|
-
catch (err) {
|
|
106
|
-
log.logWarning("Failed to touch Gondolin heartbeat", err instanceof Error ? err.message : String(err));
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
heartbeatPath() {
|
|
110
|
-
return this.dir ? join(this.dir, "heartbeat") : undefined;
|
|
111
|
-
}
|
|
112
|
-
directory() {
|
|
113
|
-
return this.dir;
|
|
114
|
-
}
|
|
115
|
-
/**
|
|
116
|
-
* Startup reconciliation: stop VM runners orphaned by a previous owner
|
|
117
|
-
* process that died without cleanup, drop stale records, and let Gondolin
|
|
118
|
-
* collect its own stale session registry entries. Runtimes whose owner is
|
|
119
|
-
* still alive (workers surviving a mikan restart) are left for adoption.
|
|
120
|
-
* Idempotent — a second run finds an empty inventory.
|
|
121
|
-
*/
|
|
122
|
-
async reconcile() {
|
|
123
|
-
if (!this.dir)
|
|
124
|
-
return;
|
|
125
|
-
ensureDirExists(this.dir);
|
|
126
|
-
let killed = 0;
|
|
127
|
-
let stale = 0;
|
|
128
|
-
let skipped = 0;
|
|
129
|
-
for (const { record, path } of this.readRecords((badPath) => {
|
|
130
|
-
rmSync(badPath, { force: true });
|
|
131
|
-
stale += 1;
|
|
132
|
-
})) {
|
|
133
|
-
if (record.ownerPid === process.pid)
|
|
134
|
-
continue;
|
|
135
|
-
if (this.isPidAlive(record.ownerPid)) {
|
|
136
|
-
skipped += 1;
|
|
137
|
-
continue;
|
|
138
|
-
}
|
|
139
|
-
const outcome = await this.reapRecord(record, path);
|
|
140
|
-
if (outcome === "killed")
|
|
141
|
-
killed += 1;
|
|
142
|
-
else if (outcome !== "alive")
|
|
143
|
-
stale += 1;
|
|
144
|
-
}
|
|
145
|
-
try {
|
|
146
|
-
await this.gcSessionsImpl();
|
|
147
|
-
}
|
|
148
|
-
catch (err) {
|
|
149
|
-
log.logWarning("Failed to collect Gondolin session registry", err instanceof Error ? err.message : String(err));
|
|
150
|
-
}
|
|
151
|
-
log.logInfo(`Reconciled Gondolin runtimes (killed=${killed}, stale=${stale}, liveOwner=${skipped})`);
|
|
152
|
-
}
|
|
153
|
-
/**
|
|
154
|
-
* Reap one dead-owner record: stop its orphaned runner if it is still the
|
|
155
|
-
* process we launched, then drop the record (kept only while the runner
|
|
156
|
-
* refuses to die, so the next reconcile retries).
|
|
157
|
-
*/
|
|
158
|
-
async reapRecord(record, path = this.recordPath(record.sessionId)) {
|
|
159
|
-
const outcome = record.runnerPid === null ? "gone" : await this.stopOrphan(record);
|
|
160
|
-
if (outcome === "alive") {
|
|
161
|
-
log.logWarning(`Orphaned Gondolin runner ${record.runnerPid} survived SIGKILL; keeping record '${record.sessionId}'`);
|
|
162
|
-
return outcome;
|
|
163
|
-
}
|
|
164
|
-
rmSync(path, { force: true });
|
|
165
|
-
return outcome;
|
|
166
|
-
}
|
|
167
|
-
readRecords(onMalformed) {
|
|
168
|
-
if (!this.dir)
|
|
169
|
-
return [];
|
|
170
|
-
ensureDirExists(this.dir);
|
|
171
|
-
const entries = [];
|
|
172
|
-
for (const file of readdirSync(this.dir)) {
|
|
173
|
-
if (!file.endsWith(".json"))
|
|
174
|
-
continue;
|
|
175
|
-
const path = join(this.dir, file);
|
|
176
|
-
let record;
|
|
177
|
-
try {
|
|
178
|
-
record = readJsonFileIfExists(path, isGondolinRuntimeRecord, (detail) => `Malformed Gondolin runtime record at ${path}: ${detail}`);
|
|
179
|
-
}
|
|
180
|
-
catch (err) {
|
|
181
|
-
log.logWarning("Dropping malformed Gondolin runtime record", err instanceof Error ? err.message : String(err));
|
|
182
|
-
}
|
|
183
|
-
if (record)
|
|
184
|
-
entries.push({ record, path });
|
|
185
|
-
else
|
|
186
|
-
onMalformed?.(path);
|
|
187
|
-
}
|
|
188
|
-
return entries;
|
|
189
|
-
}
|
|
190
|
-
async stopOrphan(record) {
|
|
191
|
-
const pid = record.runnerPid;
|
|
192
|
-
if (!this.isPidAlive(pid))
|
|
193
|
-
return "gone";
|
|
194
|
-
// Guard against pid reuse: only kill processes that still look like a
|
|
195
|
-
// Gondolin runner (its guest assets and overlay disk live under paths
|
|
196
|
-
// containing "gondolin").
|
|
197
|
-
let command;
|
|
198
|
-
try {
|
|
199
|
-
command = await this.execFileImpl("ps", ["-p", String(pid), "-o", "command="]);
|
|
200
|
-
}
|
|
201
|
-
catch {
|
|
202
|
-
return "gone";
|
|
203
|
-
}
|
|
204
|
-
if (!command.toLowerCase().includes("gondolin")) {
|
|
205
|
-
log.logWarning(`Pid ${pid} from Gondolin runtime record '${record.sessionId}' no longer looks like a Gondolin runner; skipping kill`);
|
|
206
|
-
return "reused";
|
|
207
|
-
}
|
|
208
|
-
log.logInfo(`Stopping orphaned Gondolin runner ${pid} (session '${record.sessionId}', instance '${record.instanceId}')`);
|
|
209
|
-
this.signal(pid, "SIGTERM");
|
|
210
|
-
if (await this.waitForExit(pid))
|
|
211
|
-
return "killed";
|
|
212
|
-
this.signal(pid, "SIGKILL");
|
|
213
|
-
return (await this.waitForExit(pid)) ? "killed" : "alive";
|
|
214
|
-
}
|
|
215
|
-
signal(pid, signal) {
|
|
216
|
-
try {
|
|
217
|
-
this.killImpl(pid, signal);
|
|
218
|
-
}
|
|
219
|
-
catch {
|
|
220
|
-
// process already gone
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
isPidAlive(pid) {
|
|
224
|
-
try {
|
|
225
|
-
this.killImpl(pid, 0);
|
|
226
|
-
return true;
|
|
227
|
-
}
|
|
228
|
-
catch {
|
|
229
|
-
return false;
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
async waitForExit(pid) {
|
|
233
|
-
const deadline = Date.now() + this.killWaitMs;
|
|
234
|
-
while (this.isPidAlive(pid)) {
|
|
235
|
-
if (Date.now() >= deadline)
|
|
236
|
-
return false;
|
|
237
|
-
await new Promise((resolve) => setTimeout(resolve, this.killPollIntervalMs));
|
|
238
|
-
}
|
|
239
|
-
return true;
|
|
240
|
-
}
|
|
241
|
-
write(record) {
|
|
242
|
-
if (!this.dir)
|
|
243
|
-
return;
|
|
244
|
-
try {
|
|
245
|
-
ensureDirExists(this.dir);
|
|
246
|
-
writeFileSync(this.recordPath(record.sessionId), JSON.stringify(record, null, 2) + "\n");
|
|
247
|
-
}
|
|
248
|
-
catch (err) {
|
|
249
|
-
log.logWarning(`Failed to persist Gondolin runtime record '${record.sessionId}'`, err instanceof Error ? err.message : String(err));
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
recordPath(sessionId) {
|
|
253
|
-
return join(this.dir, `${sessionId}.json`);
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
export const gondolinInventory = new GondolinRuntimeInventory();
|
|
257
|
-
//# sourceMappingURL=gondolin-inventory.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"gondolin-inventory.js","sourceRoot":"","sources":["../../src/sandbox/gondolin-inventory.ts"],"names":[],"mappings":"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","sourcesContent":["import { readdirSync, rmSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport * as log from \"../log.js\";\nimport { ensureDirExists, readJsonFileIfExists } from \"../utils/file-guards.js\";\nimport { execSimple } from \"./utils.js\";\n\n/**\n * Durable record of one Gondolin runtime. Written beside the state dir (never\n * inside the workspace) by whichever process owns the VM — the worker process\n * hosting it — so a crash that skips every exit hook still leaves enough\n * behind to adopt the runtime back or stop its orphaned VM runner.\n */\n// The record is a wire contract shape shared with the Go daemon's\n// rediscovery; its single home is gondolin-contract.ts.\nexport type { GondolinRuntimeRecord } from \"./gondolin-contract.js\";\nimport { isGondolinRuntimeRecord, type GondolinRuntimeRecord } from \"./gondolin-contract.js\";\n\ninterface GondolinInventoryOverrides {\n execFile?: (cmd: string, args: string[]) => Promise<string>;\n kill?: (pid: number, signal: NodeJS.Signals | 0) => void;\n gcSessions?: () => Promise<number>;\n killWaitMs?: number;\n killPollIntervalMs?: number;\n}\n\nasync function defaultGcSessions(): Promise<number> {\n const { gcSessions } = await import(\"@earendil-works/gondolin\");\n return gcSessions();\n}\n\nclass GondolinRuntimeInventory {\n private dir?: string;\n private execFileImpl = execSimple;\n private killImpl: (pid: number, signal: NodeJS.Signals | 0) => void = (pid, signal) =>\n process.kill(pid, signal);\n private gcSessionsImpl = defaultGcSessions;\n private killWaitMs = 2000;\n private killPollIntervalMs = 100;\n private readonly liveRecords = new Map<string, GondolinRuntimeRecord>();\n\n configure(dir?: string, overrides?: GondolinInventoryOverrides): void {\n this.dir = dir;\n this.execFileImpl = overrides?.execFile ?? execSimple;\n this.killImpl = overrides?.kill ?? ((pid, signal) => process.kill(pid, signal));\n this.gcSessionsImpl = overrides?.gcSessions ?? defaultGcSessions;\n this.killWaitMs = overrides?.killWaitMs ?? 2000;\n this.killPollIntervalMs = overrides?.killPollIntervalMs ?? 100;\n this.liveRecords.clear();\n }\n\n record(entry: {\n sessionId: string;\n instanceId: string;\n runnerPid: number | null;\n socketPath?: string;\n fingerprint?: string;\n }): void {\n if (!this.dir) return;\n const record: GondolinRuntimeRecord = {\n ...entry,\n ownerPid: process.pid,\n createdAt: new Date().toISOString(),\n };\n this.liveRecords.set(entry.sessionId, record);\n this.write(record);\n }\n\n /** Re-persist a runtime whose runner pid changed (late boot, runner restart). */\n refresh(sessionId: string, runnerPid: number | null): void {\n const record = this.liveRecords.get(sessionId);\n if (!record || record.runnerPid === runnerPid) return;\n record.runnerPid = runnerPid;\n this.write(record);\n }\n\n release(sessionId: string): void {\n this.liveRecords.delete(sessionId);\n if (!this.dir) return;\n try {\n rmSync(this.recordPath(sessionId), { force: true });\n } catch (err) {\n log.logWarning(\n `Failed to release Gondolin runtime record '${sessionId}'`,\n err instanceof Error ? err.message : String(err),\n );\n }\n }\n\n /**\n * Newest worker-owned record for a session key whose owner is still alive —\n * a candidate for adoption instead of spawning a fresh worker. Records left\n * by dead owners are reaped on the spot.\n */\n async findAdoptable(instanceId: string): Promise<GondolinRuntimeRecord | undefined> {\n let newest: GondolinRuntimeRecord | undefined;\n for (const { record, path } of this.readRecords()) {\n if (record.instanceId !== instanceId || !record.socketPath) continue;\n if (!this.isPidAlive(record.ownerPid)) {\n await this.reapRecord(record, path);\n continue;\n }\n if (!newest || record.createdAt > newest.createdAt) newest = record;\n }\n return newest;\n }\n\n /** Reap one session's record if its owner is gone (e.g. after force-killing a worker). */\n async reapSession(sessionId: string): Promise<void> {\n for (const { record, path } of this.readRecords()) {\n if (record.sessionId !== sessionId) continue;\n if (!this.isPidAlive(record.ownerPid)) await this.reapRecord(record, path);\n return;\n }\n }\n\n /** Worker-owned records with live owners (runtimes surviving from a previous mikan). */\n listWorkerRecords(): GondolinRuntimeRecord[] {\n return this.readRecords()\n .map(({ record }) => record)\n .filter((record) => record.socketPath !== undefined && this.isPidAlive(record.ownerPid));\n }\n\n /**\n * mikan liveness signal for workers: a worker whose heartbeat goes stale\n * knows every mikan that could adopt it is gone and shuts itself down.\n */\n touchHeartbeat(): void {\n if (!this.dir) return;\n try {\n ensureDirExists(this.dir);\n writeFileSync(this.heartbeatPath() as string, new Date().toISOString() + \"\\n\");\n } catch (err) {\n log.logWarning(\n \"Failed to touch Gondolin heartbeat\",\n err instanceof Error ? err.message : String(err),\n );\n }\n }\n\n heartbeatPath(): string | undefined {\n return this.dir ? join(this.dir, \"heartbeat\") : undefined;\n }\n\n directory(): string | undefined {\n return this.dir;\n }\n\n /**\n * Startup reconciliation: stop VM runners orphaned by a previous owner\n * process that died without cleanup, drop stale records, and let Gondolin\n * collect its own stale session registry entries. Runtimes whose owner is\n * still alive (workers surviving a mikan restart) are left for adoption.\n * Idempotent — a second run finds an empty inventory.\n */\n async reconcile(): Promise<void> {\n if (!this.dir) return;\n ensureDirExists(this.dir);\n let killed = 0;\n let stale = 0;\n let skipped = 0;\n for (const { record, path } of this.readRecords((badPath) => {\n rmSync(badPath, { force: true });\n stale += 1;\n })) {\n if (record.ownerPid === process.pid) continue;\n if (this.isPidAlive(record.ownerPid)) {\n skipped += 1;\n continue;\n }\n const outcome = await this.reapRecord(record, path);\n if (outcome === \"killed\") killed += 1;\n else if (outcome !== \"alive\") stale += 1;\n }\n try {\n await this.gcSessionsImpl();\n } catch (err) {\n log.logWarning(\n \"Failed to collect Gondolin session registry\",\n err instanceof Error ? err.message : String(err),\n );\n }\n log.logInfo(\n `Reconciled Gondolin runtimes (killed=${killed}, stale=${stale}, liveOwner=${skipped})`,\n );\n }\n\n /**\n * Reap one dead-owner record: stop its orphaned runner if it is still the\n * process we launched, then drop the record (kept only while the runner\n * refuses to die, so the next reconcile retries).\n */\n async reapRecord(\n record: GondolinRuntimeRecord,\n path = this.recordPath(record.sessionId),\n ): Promise<\"killed\" | \"gone\" | \"reused\" | \"alive\"> {\n const outcome = record.runnerPid === null ? \"gone\" : await this.stopOrphan(record);\n if (outcome === \"alive\") {\n log.logWarning(\n `Orphaned Gondolin runner ${record.runnerPid} survived SIGKILL; keeping record '${record.sessionId}'`,\n );\n return outcome;\n }\n rmSync(path, { force: true });\n return outcome;\n }\n\n private readRecords(\n onMalformed?: (path: string) => void,\n ): Array<{ record: GondolinRuntimeRecord; path: string }> {\n if (!this.dir) return [];\n ensureDirExists(this.dir);\n const entries: Array<{ record: GondolinRuntimeRecord; path: string }> = [];\n for (const file of readdirSync(this.dir)) {\n if (!file.endsWith(\".json\")) continue;\n const path = join(this.dir, file);\n let record: GondolinRuntimeRecord | undefined;\n try {\n record = readJsonFileIfExists(\n path,\n isGondolinRuntimeRecord,\n (detail) => `Malformed Gondolin runtime record at ${path}: ${detail}`,\n );\n } catch (err) {\n log.logWarning(\n \"Dropping malformed Gondolin runtime record\",\n err instanceof Error ? err.message : String(err),\n );\n }\n if (record) entries.push({ record, path });\n else onMalformed?.(path);\n }\n return entries;\n }\n\n private async stopOrphan(\n record: GondolinRuntimeRecord,\n ): Promise<\"killed\" | \"gone\" | \"reused\" | \"alive\"> {\n const pid = record.runnerPid as number;\n if (!this.isPidAlive(pid)) return \"gone\";\n // Guard against pid reuse: only kill processes that still look like a\n // Gondolin runner (its guest assets and overlay disk live under paths\n // containing \"gondolin\").\n let command: string;\n try {\n command = await this.execFileImpl(\"ps\", [\"-p\", String(pid), \"-o\", \"command=\"]);\n } catch {\n return \"gone\";\n }\n if (!command.toLowerCase().includes(\"gondolin\")) {\n log.logWarning(\n `Pid ${pid} from Gondolin runtime record '${record.sessionId}' no longer looks like a Gondolin runner; skipping kill`,\n );\n return \"reused\";\n }\n log.logInfo(\n `Stopping orphaned Gondolin runner ${pid} (session '${record.sessionId}', instance '${record.instanceId}')`,\n );\n this.signal(pid, \"SIGTERM\");\n if (await this.waitForExit(pid)) return \"killed\";\n this.signal(pid, \"SIGKILL\");\n return (await this.waitForExit(pid)) ? \"killed\" : \"alive\";\n }\n\n private signal(pid: number, signal: NodeJS.Signals): void {\n try {\n this.killImpl(pid, signal);\n } catch {\n // process already gone\n }\n }\n\n isPidAlive(pid: number): boolean {\n try {\n this.killImpl(pid, 0);\n return true;\n } catch {\n return false;\n }\n }\n\n private async waitForExit(pid: number): Promise<boolean> {\n const deadline = Date.now() + this.killWaitMs;\n while (this.isPidAlive(pid)) {\n if (Date.now() >= deadline) return false;\n await new Promise((resolve) => setTimeout(resolve, this.killPollIntervalMs));\n }\n return true;\n }\n\n private write(record: GondolinRuntimeRecord): void {\n if (!this.dir) return;\n try {\n ensureDirExists(this.dir);\n writeFileSync(this.recordPath(record.sessionId), JSON.stringify(record, null, 2) + \"\\n\");\n } catch (err) {\n log.logWarning(\n `Failed to persist Gondolin runtime record '${record.sessionId}'`,\n err instanceof Error ? err.message : String(err),\n );\n }\n }\n\n private recordPath(sessionId: string): string {\n return join(this.dir as string, `${sessionId}.json`);\n }\n}\n\nexport const gondolinInventory = new GondolinRuntimeInventory();\n"]}
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Certificate provisioning and join-token authority for dial-home workers.
|
|
3
|
-
*
|
|
4
|
-
* Owns a private CA under the state dir (`gondolin-gateway/`), auto-issues the
|
|
5
|
-
* gateway's own server certificate from it, and exchanges one-time join
|
|
6
|
-
* tokens for CA-signed worker client certificates — the kubeadm-style
|
|
7
|
-
* bootstrap that replaces hand-provisioned mTLS material. All X.509 issuance
|
|
8
|
-
* shells out to openssl (Node can parse but not issue certificates).
|
|
9
|
-
*/
|
|
10
|
-
declare class GondolinJoinService {
|
|
11
|
-
private dir?;
|
|
12
|
-
private execImpl;
|
|
13
|
-
private now;
|
|
14
|
-
private tokenTtlSeconds;
|
|
15
|
-
configure(dir?: string, overrides?: {
|
|
16
|
-
execFile?: (cmd: string, args: string[]) => Promise<unknown>;
|
|
17
|
-
now?: () => number;
|
|
18
|
-
tokenTtlSeconds?: number;
|
|
19
|
-
}): void;
|
|
20
|
-
isConfigured(): boolean;
|
|
21
|
-
paths(): {
|
|
22
|
-
caFile: string;
|
|
23
|
-
caKeyFile: string;
|
|
24
|
-
serverCertFile: string;
|
|
25
|
-
serverKeyFile: string;
|
|
26
|
-
};
|
|
27
|
-
/** Create the worker CA on first use; idempotent afterwards. */
|
|
28
|
-
ensureCa(): Promise<void>;
|
|
29
|
-
/**
|
|
30
|
-
* Issue the gateway's server certificate from the worker CA so joining
|
|
31
|
-
* workers can verify the host against the pinned CA. Reissued when the SAN
|
|
32
|
-
* list changes.
|
|
33
|
-
*/
|
|
34
|
-
ensureServerCert(hostnames: string[]): Promise<{
|
|
35
|
-
certFile: string;
|
|
36
|
-
keyFile: string;
|
|
37
|
-
}>;
|
|
38
|
-
/** SHA-256 of the CA certificate DER — the join command's --ca-pin. */
|
|
39
|
-
caFingerprint(): string;
|
|
40
|
-
/** Mint a single-use join token; only its hash is stored. */
|
|
41
|
-
mintToken(): Promise<{
|
|
42
|
-
token: string;
|
|
43
|
-
fingerprint: string;
|
|
44
|
-
expiresAt: number;
|
|
45
|
-
}>;
|
|
46
|
-
/** Validate and burn a join token (single use, TTL-bounded). */
|
|
47
|
-
consumeToken(token: string): boolean;
|
|
48
|
-
/** Sign a joining worker's CSR with the worker CA. */
|
|
49
|
-
signCsr(csrPem: string): Promise<{
|
|
50
|
-
certPem: string;
|
|
51
|
-
caPem: string;
|
|
52
|
-
}>;
|
|
53
|
-
private subjectAltNames;
|
|
54
|
-
private openssl;
|
|
55
|
-
private tokensPath;
|
|
56
|
-
private readTokens;
|
|
57
|
-
private writeTokens;
|
|
58
|
-
private requireDir;
|
|
59
|
-
}
|
|
60
|
-
export declare const gondolinJoin: GondolinJoinService;
|
|
61
|
-
export {};
|
|
62
|
-
//# sourceMappingURL=gondolin-join.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"gondolin-join.d.ts","sourceRoot":"","sources":["../../src/sandbox/gondolin-join.ts"],"names":[],"mappings":"AAyCA;;;;;;;;GAQG;AACH,cAAM,mBAAmB;IACvB,OAAO,CAAC,GAAG,CAAC,CAAS;IACrB,OAAO,CAAC,QAAQ,CAAoE;IACpF,OAAO,CAAC,GAAG,CAA0B;IACrC,OAAO,CAAC,eAAe,CAAqB;IAE5C,SAAS,CACP,GAAG,CAAC,EAAE,MAAM,EACZ,SAAS,CAAC,EAAE;QACV,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;QAC7D,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;QACnB,eAAe,CAAC,EAAE,MAAM,CAAC;KAC1B,GACA,IAAI,CAKN;IAED,YAAY,IAAI,OAAO,CAEtB;IAED,KAAK,IAAI;QACP,MAAM,EAAE,MAAM,CAAC;QACf,SAAS,EAAE,MAAM,CAAC;QAClB,cAAc,EAAE,MAAM,CAAC;QACvB,aAAa,EAAE,MAAM,CAAC;KACvB,CAQA;IAED,gEAAgE;IAC1D,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAsB9B;IAED;;;;OAIG;IACG,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CA0D1F;IAED,uEAAuE;IACvE,aAAa,IAAI,MAAM,CAGtB;IAED,6DAA6D;IACvD,SAAS,IAAI,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CASpF;IAED,gEAAgE;IAChE,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAcnC;IAED,sDAAsD;IAChD,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CA+BzE;IAED,OAAO,CAAC,eAAe;YAST,OAAO;IAWrB,OAAO,CAAC,UAAU;IAIlB,OAAO,CAAC,UAAU;IAkBlB,OAAO,CAAC,WAAW;IAOnB,OAAO,CAAC,UAAU;CAInB;AAED,eAAO,MAAM,YAAY,qBAA4B,CAAC"}
|