dshb-core 0.0.1
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/LICENSE +21 -0
- package/cordis.patch.yml +3 -0
- package/lib/audit.d.ts +16 -0
- package/lib/audit.js +38 -0
- package/lib/audit.js.map +1 -0
- package/lib/index-CLOUBKxU.d.ts +1125 -0
- package/lib/index.d.ts +8 -0
- package/lib/index.js +77 -0
- package/lib/index.js.map +1 -0
- package/lib/known-hosts.d.ts +23 -0
- package/lib/known-hosts.js +76 -0
- package/lib/known-hosts.js.map +1 -0
- package/lib/node-registry.d.ts +108 -0
- package/lib/node-registry.js +191 -0
- package/lib/node-registry.js.map +1 -0
- package/lib/routes.d.ts +9 -0
- package/lib/routes.js +404 -0
- package/lib/routes.js.map +1 -0
- package/lib/ssh-config.d.ts +23 -0
- package/lib/ssh-config.js +70 -0
- package/lib/ssh-config.js.map +1 -0
- package/lib/test.d.ts +43 -0
- package/lib/test.js +116 -0
- package/lib/test.js.map +1 -0
- package/lib/workspace-bindings.d.ts +28 -0
- package/lib/workspace-bindings.js +86 -0
- package/lib/workspace-bindings.js.map +1 -0
- package/lib/workspaces.d.ts +9 -0
- package/lib/workspaces.js +283 -0
- package/lib/workspaces.js.map +1 -0
- package/package.json +38 -0
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { t as Context } from "./index-CLOUBKxU.js";
|
|
2
|
+
//#region src/index.d.ts
|
|
3
|
+
declare const name = "dshb-core";
|
|
4
|
+
declare const inject: string[];
|
|
5
|
+
declare function apply(ctx: Context): void;
|
|
6
|
+
//#endregion
|
|
7
|
+
export { apply, inject, name };
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { sharedAuditLogger } from "./audit.js";
|
|
2
|
+
import { KnownHostsStore } from "./known-hosts.js";
|
|
3
|
+
import { NodeRegistry } from "./node-registry.js";
|
|
4
|
+
import { testNode } from "./test.js";
|
|
5
|
+
import { registerNodeRoutes, registerSshConfigRoutes } from "./routes.js";
|
|
6
|
+
import { registerRemoteDownloadRoutes, registerWorkspaceRoutes } from "./workspaces.js";
|
|
7
|
+
import { WorkspaceBindingsStore } from "./workspace-bindings.js";
|
|
8
|
+
import { sharedWorldResolver } from "dshb-router/resolve";
|
|
9
|
+
//#region src/index.ts
|
|
10
|
+
const name = "dshb-core";
|
|
11
|
+
const inject = ["webServer", "credentials"];
|
|
12
|
+
function apply(ctx) {
|
|
13
|
+
const knownHosts = new KnownHostsStore();
|
|
14
|
+
const registry = new NodeRegistry(ctx);
|
|
15
|
+
const bindings = new WorkspaceBindingsStore();
|
|
16
|
+
ctx.provide("nodeRegistry", registry);
|
|
17
|
+
ctx.provide("knownHosts", knownHosts);
|
|
18
|
+
ctx.provide("workspaceBindings", bindings);
|
|
19
|
+
ctx.provide("dshbAudit", sharedAuditLogger());
|
|
20
|
+
sharedWorldResolver().setBindings(bindings);
|
|
21
|
+
registerNodeRoutes(ctx, registry);
|
|
22
|
+
registerSshConfigRoutes(ctx, knownHosts);
|
|
23
|
+
registerWorkspaceRoutes(ctx, registry, bindings);
|
|
24
|
+
registerRemoteDownloadRoutes(ctx, registry, bindings);
|
|
25
|
+
warmupWorlds(ctx, registry, bindings);
|
|
26
|
+
startHeartbeat(ctx, registry);
|
|
27
|
+
reprovisionDockerNodes(ctx, registry);
|
|
28
|
+
}
|
|
29
|
+
const HEARTBEAT_MS = 3e5;
|
|
30
|
+
function startHeartbeat(ctx, registry) {
|
|
31
|
+
const tick = async () => {
|
|
32
|
+
const sshTester = ctx.get("dshbSshPool");
|
|
33
|
+
const dockerTester = ctx.get("dshbDockerTester");
|
|
34
|
+
for (const node of registry.list()) try {
|
|
35
|
+
await testNode(registry, node.id, sshTester, void 0, dockerTester);
|
|
36
|
+
} catch {}
|
|
37
|
+
};
|
|
38
|
+
const timer = setInterval(() => void tick(), HEARTBEAT_MS);
|
|
39
|
+
ctx.effect(() => () => clearInterval(timer));
|
|
40
|
+
setImmediate(() => void tick());
|
|
41
|
+
}
|
|
42
|
+
function reprovisionDockerNodes(ctx, registry) {
|
|
43
|
+
setImmediate(() => {
|
|
44
|
+
(async () => {
|
|
45
|
+
const provisioner = ctx.get("dshbDockerProvisioner");
|
|
46
|
+
if (!provisioner) return;
|
|
47
|
+
for (const node of registry.list()) {
|
|
48
|
+
if (node.type !== "local-docker" && node.type !== "remote-docker") continue;
|
|
49
|
+
if (!node.docker?.image) continue;
|
|
50
|
+
if (node.docker?.containerId) continue;
|
|
51
|
+
try {
|
|
52
|
+
await provisioner.provision(node.id);
|
|
53
|
+
} catch {}
|
|
54
|
+
}
|
|
55
|
+
})();
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
function warmupWorlds(ctx, registry, bindings) {
|
|
59
|
+
setImmediate(() => {
|
|
60
|
+
(async () => {
|
|
61
|
+
for (const b of bindings.list()) {
|
|
62
|
+
const node = registry.get(b.nodeId);
|
|
63
|
+
if (!node || node.type === "local-host") continue;
|
|
64
|
+
try {
|
|
65
|
+
const worlds = node.type === "local-docker" || node.type === "remote-docker" ? ctx.get("dshbDockerWorldsGeneric") : ctx.get("dshbWorlds");
|
|
66
|
+
if (!worlds) continue;
|
|
67
|
+
const world = await worlds.ensure(b.nodeId);
|
|
68
|
+
if (world) await world.ensureDir(b.remotePath);
|
|
69
|
+
} catch {}
|
|
70
|
+
}
|
|
71
|
+
})();
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
//#endregion
|
|
75
|
+
export { apply, inject, name };
|
|
76
|
+
|
|
77
|
+
//# sourceMappingURL=index.js.map
|
package/lib/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import type { Context } from '@deepseek-ai/cordis'\nimport { sharedWorldResolver } from 'dshb-router/resolve'\nimport type {} from '@deepseek-ai/dsh-host-webserver'\nimport { sharedAuditLogger } from './audit.js'\nimport { KnownHostsStore } from './known-hosts.js'\nimport { NodeRegistry } from './node-registry.js'\nimport { registerNodeRoutes, registerSshConfigRoutes } from './routes.js'\nimport { registerWorkspaceRoutes, registerRemoteDownloadRoutes } from './workspaces.js'\nimport { testNode, type DockerNodeTester, type SshHandshakeTester } from './test.js'\nimport { WorkspaceBindingsStore } from './workspace-bindings.js'\n\nexport const name = 'dshb-core'\n\nexport const inject = ['webServer', 'credentials']\n\nexport function apply(ctx: Context): void {\n const knownHosts = new KnownHostsStore()\n const registry = new NodeRegistry(ctx)\n const bindings = new WorkspaceBindingsStore()\n ctx.provide('nodeRegistry', registry)\n ctx.provide('knownHosts', knownHosts)\n ctx.provide('workspaceBindings', bindings)\n ctx.provide('dshbAudit', sharedAuditLogger())\n sharedWorldResolver().setBindings(bindings)\n void registerNodeRoutes(ctx, registry)\n void registerSshConfigRoutes(ctx, knownHosts)\n void registerWorkspaceRoutes(ctx, registry, bindings)\n void registerRemoteDownloadRoutes(ctx, registry, bindings)\n warmupWorlds(ctx, registry, bindings)\n startHeartbeat(ctx, registry)\n reprovisionDockerNodes(ctx, registry)\n}\n\nconst HEARTBEAT_MS = 5 * 60 * 1000\n\nfunction startHeartbeat(ctx: Context, registry: NodeRegistry): void {\n const tick = async (): Promise<void> => {\n const sshTester = ctx.get('dshbSshPool') as SshHandshakeTester | undefined\n const dockerTester = ctx.get('dshbDockerTester') as DockerNodeTester | undefined\n for (const node of registry.list()) {\n try {\n await testNode(registry, node.id, sshTester, undefined, dockerTester)\n } catch {\n // 单节点心跳失败不影响其他节点\n }\n }\n }\n const timer = setInterval(() => void tick(), HEARTBEAT_MS)\n ctx.effect(() => () => clearInterval(timer))\n setImmediate(() => void tick())\n}\n\nfunction reprovisionDockerNodes(ctx: Context, registry: NodeRegistry): void {\n setImmediate(() => {\n void (async () => {\n const provisioner = ctx.get('dshbDockerProvisioner') as { provision(nodeId: string): Promise<unknown> } | undefined\n if (!provisioner) return\n for (const node of registry.list()) {\n if (node.type !== 'local-docker' && node.type !== 'remote-docker') continue\n if (!node.docker?.image) continue\n if (node.docker?.containerId) continue\n try {\n await provisioner.provision(node.id)\n } catch {\n // 失败已记录到 provisionStatuses\n }\n }\n })()\n })\n}\n\ninterface WarmupWorldsLike {\n ensure(nodeId: string): Promise<{ ensureDir(path: string): Promise<void> } | undefined>\n}\n\nfunction warmupWorlds(ctx: Context, registry: NodeRegistry, bindings: WorkspaceBindingsStore): void {\n setImmediate(() => {\n void (async () => {\n for (const b of bindings.list()) {\n const node = registry.get(b.nodeId)\n if (!node || node.type === 'local-host') continue\n try {\n const isDocker = node.type === 'local-docker' || node.type === 'remote-docker'\n const worlds = isDocker\n ? (ctx.get('dshbDockerWorldsGeneric') as WarmupWorldsLike | undefined)\n : (ctx.get('dshbWorlds') as WarmupWorldsLike | undefined)\n if (!worlds) continue\n const world = await worlds.ensure(b.nodeId)\n if (world) await world.ensureDir(b.remotePath)\n } catch {\n // 预热失败不应阻塞启动;运行时可再次 ensure\n }\n }\n })()\n })\n}\n"],"mappings":";;;;;;;;;AAWA,MAAa,OAAO;AAEpB,MAAa,SAAS,CAAC,aAAa,aAAa;AAEjD,SAAgB,MAAM,KAAoB;CACxC,MAAM,aAAa,IAAI,gBAAgB;CACvC,MAAM,WAAW,IAAI,aAAa,GAAG;CACrC,MAAM,WAAW,IAAI,uBAAuB;CAC5C,IAAI,QAAQ,gBAAgB,QAAQ;CACpC,IAAI,QAAQ,cAAc,UAAU;CACpC,IAAI,QAAQ,qBAAqB,QAAQ;CACzC,IAAI,QAAQ,aAAa,kBAAkB,CAAC;CAC5C,oBAAoB,CAAC,CAAC,YAAY,QAAQ;CAC1C,mBAAwB,KAAK,QAAQ;CACrC,wBAA6B,KAAK,UAAU;CAC5C,wBAA6B,KAAK,UAAU,QAAQ;CACpD,6BAAkC,KAAK,UAAU,QAAQ;CACzD,aAAa,KAAK,UAAU,QAAQ;CACpC,eAAe,KAAK,QAAQ;CAC5B,uBAAuB,KAAK,QAAQ;AACtC;AAEA,MAAM,eAAe;AAErB,SAAS,eAAe,KAAc,UAA8B;CAClE,MAAM,OAAO,YAA2B;EACtC,MAAM,YAAY,IAAI,IAAI,aAAa;EACvC,MAAM,eAAe,IAAI,IAAI,kBAAkB;EAC/C,KAAK,MAAM,QAAQ,SAAS,KAAK,GAC/B,IAAI;GACF,MAAM,SAAS,UAAU,KAAK,IAAI,WAAW,KAAA,GAAW,YAAY;EACtE,QAAQ,CAER;CAEJ;CACA,MAAM,QAAQ,kBAAkB,KAAK,KAAK,GAAG,YAAY;CACzD,IAAI,mBAAmB,cAAc,KAAK,CAAC;CAC3C,mBAAmB,KAAK,KAAK,CAAC;AAChC;AAEA,SAAS,uBAAuB,KAAc,UAA8B;CAC1E,mBAAmB;EACjB,CAAM,YAAY;GAChB,MAAM,cAAc,IAAI,IAAI,uBAAuB;GACnD,IAAI,CAAC,aAAa;GAClB,KAAK,MAAM,QAAQ,SAAS,KAAK,GAAG;IAClC,IAAI,KAAK,SAAS,kBAAkB,KAAK,SAAS,iBAAiB;IACnE,IAAI,CAAC,KAAK,QAAQ,OAAO;IACzB,IAAI,KAAK,QAAQ,aAAa;IAC9B,IAAI;KACF,MAAM,YAAY,UAAU,KAAK,EAAE;IACrC,QAAQ,CAER;GACF;EACF,EAAA,CAAG;CACL,CAAC;AACH;AAMA,SAAS,aAAa,KAAc,UAAwB,UAAwC;CAClG,mBAAmB;EACjB,CAAM,YAAY;GAChB,KAAK,MAAM,KAAK,SAAS,KAAK,GAAG;IAC/B,MAAM,OAAO,SAAS,IAAI,EAAE,MAAM;IAClC,IAAI,CAAC,QAAQ,KAAK,SAAS,cAAc;IACzC,IAAI;KAEF,MAAM,SADW,KAAK,SAAS,kBAAkB,KAAK,SAAS,kBAE1D,IAAI,IAAI,yBAAyB,IACjC,IAAI,IAAI,YAAY;KACzB,IAAI,CAAC,QAAQ;KACb,MAAM,QAAQ,MAAM,OAAO,OAAO,EAAE,MAAM;KAC1C,IAAI,OAAO,MAAM,MAAM,UAAU,EAAE,UAAU;IAC/C,QAAQ,CAER;GACF;EACF,EAAA,CAAG;CACL,CAAC;AACH"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
//#region src/known-hosts.d.ts
|
|
2
|
+
type HostKeyDecision = 'accept-new' | 'verify' | 'reject';
|
|
3
|
+
interface KnownHostEntry {
|
|
4
|
+
fingerprint: string;
|
|
5
|
+
recordedAt: string;
|
|
6
|
+
}
|
|
7
|
+
declare class KnownHostsStore {
|
|
8
|
+
private entries;
|
|
9
|
+
private loaded;
|
|
10
|
+
private file;
|
|
11
|
+
private load;
|
|
12
|
+
private save;
|
|
13
|
+
key(host: string, port: number): string;
|
|
14
|
+
get(host: string, port: number): KnownHostEntry | undefined;
|
|
15
|
+
record(host: string, port: number, fingerprint: string): void;
|
|
16
|
+
forget(host: string, port: number): void;
|
|
17
|
+
exists(): boolean;
|
|
18
|
+
check(host: string, port: number, fingerprint: string, mode?: HostKeyDecision): HostKeyDecision;
|
|
19
|
+
list(): Record<string, KnownHostEntry>;
|
|
20
|
+
}
|
|
21
|
+
//#endregion
|
|
22
|
+
export { HostKeyDecision, KnownHostEntry, KnownHostsStore };
|
|
23
|
+
//# sourceMappingURL=known-hosts.d.ts.map
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
//#region src/known-hosts.ts
|
|
5
|
+
var KnownHostsStore = class {
|
|
6
|
+
entries = {};
|
|
7
|
+
loaded = false;
|
|
8
|
+
file() {
|
|
9
|
+
const home = process.env.DSH_HOME ?? join(homedir(), ".dsh");
|
|
10
|
+
return join(home, "dshb", "known_hosts.json");
|
|
11
|
+
}
|
|
12
|
+
load() {
|
|
13
|
+
if (this.loaded) return;
|
|
14
|
+
this.loaded = true;
|
|
15
|
+
try {
|
|
16
|
+
this.entries = JSON.parse(readFileSync(this.file(), "utf8"));
|
|
17
|
+
} catch {
|
|
18
|
+
this.entries = {};
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
save() {
|
|
22
|
+
const file = this.file();
|
|
23
|
+
mkdirSync(join(file, ".."), {
|
|
24
|
+
recursive: true,
|
|
25
|
+
mode: 448
|
|
26
|
+
});
|
|
27
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
28
|
+
writeFileSync(tmp, JSON.stringify(this.entries, null, 2), { mode: 384 });
|
|
29
|
+
renameSync(tmp, file);
|
|
30
|
+
chmodSync(file, 384);
|
|
31
|
+
}
|
|
32
|
+
key(host, port) {
|
|
33
|
+
return `${host}:${port}`;
|
|
34
|
+
}
|
|
35
|
+
get(host, port) {
|
|
36
|
+
this.load();
|
|
37
|
+
return this.entries[this.key(host, port)];
|
|
38
|
+
}
|
|
39
|
+
record(host, port, fingerprint) {
|
|
40
|
+
this.load();
|
|
41
|
+
this.entries[this.key(host, port)] = {
|
|
42
|
+
fingerprint,
|
|
43
|
+
recordedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
44
|
+
};
|
|
45
|
+
this.save();
|
|
46
|
+
}
|
|
47
|
+
forget(host, port) {
|
|
48
|
+
this.load();
|
|
49
|
+
delete this.entries[this.key(host, port)];
|
|
50
|
+
this.save();
|
|
51
|
+
}
|
|
52
|
+
exists() {
|
|
53
|
+
return existsSync(this.file());
|
|
54
|
+
}
|
|
55
|
+
check(host, port, fingerprint, mode = "accept-new") {
|
|
56
|
+
this.load();
|
|
57
|
+
const entry = this.entries[this.key(host, port)];
|
|
58
|
+
if (!entry) {
|
|
59
|
+
if (mode === "accept-new") {
|
|
60
|
+
this.record(host, port, fingerprint);
|
|
61
|
+
return "accept-new";
|
|
62
|
+
}
|
|
63
|
+
return "reject";
|
|
64
|
+
}
|
|
65
|
+
if (entry.fingerprint === fingerprint) return "verify";
|
|
66
|
+
return "reject";
|
|
67
|
+
}
|
|
68
|
+
list() {
|
|
69
|
+
this.load();
|
|
70
|
+
return { ...this.entries };
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
//#endregion
|
|
74
|
+
export { KnownHostsStore };
|
|
75
|
+
|
|
76
|
+
//# sourceMappingURL=known-hosts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"known-hosts.js","names":[],"sources":["../src/known-hosts.ts"],"sourcesContent":["import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { join } from 'node:path'\n\nexport type HostKeyDecision = 'accept-new' | 'verify' | 'reject'\n\nexport interface KnownHostEntry {\n fingerprint: string\n recordedAt: string\n}\n\nexport class KnownHostsStore {\n private entries: Record<string, KnownHostEntry> = {}\n private loaded = false\n\n private file(): string {\n const home = process.env.DSH_HOME ?? join(homedir(), '.dsh')\n return join(home, 'dshb', 'known_hosts.json')\n }\n\n private load(): void {\n if (this.loaded) return\n this.loaded = true\n try {\n this.entries = JSON.parse(readFileSync(this.file(), 'utf8')) as Record<string, KnownHostEntry>\n } catch {\n this.entries = {}\n }\n }\n\n private save(): void {\n const file = this.file()\n mkdirSync(join(file, '..'), { recursive: true, mode: 0o700 })\n const tmp = `${file}.${process.pid}.tmp`\n writeFileSync(tmp, JSON.stringify(this.entries, null, 2), { mode: 0o600 })\n renameSync(tmp, file)\n chmodSync(file, 0o600)\n }\n\n key(host: string, port: number): string {\n return `${host}:${port}`\n }\n\n get(host: string, port: number): KnownHostEntry | undefined {\n this.load()\n return this.entries[this.key(host, port)]\n }\n\n record(host: string, port: number, fingerprint: string): void {\n this.load()\n this.entries[this.key(host, port)] = { fingerprint, recordedAt: new Date().toISOString() }\n this.save()\n }\n\n forget(host: string, port: number): void {\n this.load()\n delete this.entries[this.key(host, port)]\n this.save()\n }\n\n exists(): boolean {\n return existsSync(this.file())\n }\n\n check(host: string, port: number, fingerprint: string, mode: HostKeyDecision = 'accept-new'): HostKeyDecision {\n this.load()\n const entry = this.entries[this.key(host, port)]\n if (!entry) {\n if (mode === 'accept-new') {\n this.record(host, port, fingerprint)\n return 'accept-new'\n }\n return 'reject'\n }\n if (entry.fingerprint === fingerprint) return 'verify'\n return 'reject'\n }\n\n list(): Record<string, KnownHostEntry> {\n this.load()\n return { ...this.entries }\n }\n}\n"],"mappings":";;;;AAWA,IAAa,kBAAb,MAA6B;CAC3B,UAAkD,CAAC;CACnD,SAAiB;CAEjB,OAAuB;EACrB,MAAM,OAAO,QAAQ,IAAI,YAAY,KAAK,QAAQ,GAAG,MAAM;EAC3D,OAAO,KAAK,MAAM,QAAQ,kBAAkB;CAC9C;CAEA,OAAqB;EACnB,IAAI,KAAK,QAAQ;EACjB,KAAK,SAAS;EACd,IAAI;GACF,KAAK,UAAU,KAAK,MAAM,aAAa,KAAK,KAAK,GAAG,MAAM,CAAC;EAC7D,QAAQ;GACN,KAAK,UAAU,CAAC;EAClB;CACF;CAEA,OAAqB;EACnB,MAAM,OAAO,KAAK,KAAK;EACvB,UAAU,KAAK,MAAM,IAAI,GAAG;GAAE,WAAW;GAAM,MAAM;EAAM,CAAC;EAC5D,MAAM,MAAM,GAAG,KAAK,GAAG,QAAQ,IAAI;EACnC,cAAc,KAAK,KAAK,UAAU,KAAK,SAAS,MAAM,CAAC,GAAG,EAAE,MAAM,IAAM,CAAC;EACzE,WAAW,KAAK,IAAI;EACpB,UAAU,MAAM,GAAK;CACvB;CAEA,IAAI,MAAc,MAAsB;EACtC,OAAO,GAAG,KAAK,GAAG;CACpB;CAEA,IAAI,MAAc,MAA0C;EAC1D,KAAK,KAAK;EACV,OAAO,KAAK,QAAQ,KAAK,IAAI,MAAM,IAAI;CACzC;CAEA,OAAO,MAAc,MAAc,aAA2B;EAC5D,KAAK,KAAK;EACV,KAAK,QAAQ,KAAK,IAAI,MAAM,IAAI,KAAK;GAAE;GAAa,6BAAY,IAAI,KAAK,EAAA,CAAE,YAAY;EAAE;EACzF,KAAK,KAAK;CACZ;CAEA,OAAO,MAAc,MAAoB;EACvC,KAAK,KAAK;EACV,OAAO,KAAK,QAAQ,KAAK,IAAI,MAAM,IAAI;EACvC,KAAK,KAAK;CACZ;CAEA,SAAkB;EAChB,OAAO,WAAW,KAAK,KAAK,CAAC;CAC/B;CAEA,MAAM,MAAc,MAAc,aAAqB,OAAwB,cAA+B;EAC5G,KAAK,KAAK;EACV,MAAM,QAAQ,KAAK,QAAQ,KAAK,IAAI,MAAM,IAAI;EAC9C,IAAI,CAAC,OAAO;GACV,IAAI,SAAS,cAAc;IACzB,KAAK,OAAO,MAAM,MAAM,WAAW;IACnC,OAAO;GACT;GACA,OAAO;EACT;EACA,IAAI,MAAM,gBAAgB,aAAa,OAAO;EAC9C,OAAO;CACT;CAEA,OAAuC;EACrC,KAAK,KAAK;EACV,OAAO,EAAE,GAAG,KAAK,QAAQ;CAC3B;AACF"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { t as Context } from "./index-CLOUBKxU.js";
|
|
2
|
+
import { CredentialKey } from "@deepseek-ai/dsh-credentials";
|
|
3
|
+
//#region src/node-registry.d.ts
|
|
4
|
+
type NodeType = 'local-host' | 'local-docker' | 'remote-ssh' | 'remote-docker';
|
|
5
|
+
interface NodeSshAuth {
|
|
6
|
+
kind: 'password' | 'key' | 'agent';
|
|
7
|
+
keyPath?: string;
|
|
8
|
+
}
|
|
9
|
+
interface NodeJumpHop {
|
|
10
|
+
host: string;
|
|
11
|
+
port?: number;
|
|
12
|
+
username?: string;
|
|
13
|
+
keyPath?: string;
|
|
14
|
+
}
|
|
15
|
+
interface NodeSsh {
|
|
16
|
+
host: string;
|
|
17
|
+
port: number;
|
|
18
|
+
username: string;
|
|
19
|
+
auth: NodeSshAuth;
|
|
20
|
+
jump?: NodeJumpHop[];
|
|
21
|
+
hostKeyFingerprint?: string;
|
|
22
|
+
}
|
|
23
|
+
interface NodeDocker {
|
|
24
|
+
mode: 'existing' | 'managed';
|
|
25
|
+
containerId?: string;
|
|
26
|
+
image?: string;
|
|
27
|
+
resources?: {
|
|
28
|
+
cpus?: number;
|
|
29
|
+
memoryMB?: number;
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
interface NodeProfile {
|
|
33
|
+
id: string;
|
|
34
|
+
name: string;
|
|
35
|
+
type: NodeType;
|
|
36
|
+
ssh?: NodeSsh;
|
|
37
|
+
docker?: NodeDocker;
|
|
38
|
+
createdAt: string;
|
|
39
|
+
updatedAt: string;
|
|
40
|
+
}
|
|
41
|
+
interface NodeSecrets {
|
|
42
|
+
password?: string;
|
|
43
|
+
privateKey?: string;
|
|
44
|
+
passphrase?: string;
|
|
45
|
+
}
|
|
46
|
+
interface NodeStatus {
|
|
47
|
+
reachable?: boolean;
|
|
48
|
+
lastCheckedAt?: string;
|
|
49
|
+
error?: string;
|
|
50
|
+
}
|
|
51
|
+
interface NodeTestReport {
|
|
52
|
+
ok: boolean;
|
|
53
|
+
reachable?: boolean;
|
|
54
|
+
error?: string;
|
|
55
|
+
category?: string;
|
|
56
|
+
}
|
|
57
|
+
interface NodeCreateInput {
|
|
58
|
+
name: string;
|
|
59
|
+
type: NodeType;
|
|
60
|
+
ssh?: Omit<NodeSsh, 'hostKeyFingerprint'> & {
|
|
61
|
+
hostKeyFingerprint?: string;
|
|
62
|
+
};
|
|
63
|
+
docker?: NodeDocker;
|
|
64
|
+
secrets?: NodeSecrets;
|
|
65
|
+
}
|
|
66
|
+
interface NodeUpdateInput {
|
|
67
|
+
name?: string;
|
|
68
|
+
ssh?: Partial<Omit<NodeSsh, 'hostKeyFingerprint'>>;
|
|
69
|
+
docker?: Partial<NodeDocker>;
|
|
70
|
+
secrets?: NodeSecrets;
|
|
71
|
+
}
|
|
72
|
+
interface CredentialLike {
|
|
73
|
+
readRecord(key: CredentialKey): Promise<unknown>;
|
|
74
|
+
modifyRecord(key: CredentialKey, mutate: (current: unknown) => Promise<unknown>): Promise<unknown>;
|
|
75
|
+
deleteRecord(key: CredentialKey): Promise<void>;
|
|
76
|
+
}
|
|
77
|
+
declare function slugifyName(name: string): string;
|
|
78
|
+
declare function dshHome(): string;
|
|
79
|
+
declare class NodeRegistry {
|
|
80
|
+
private readonly ctx;
|
|
81
|
+
private profiles;
|
|
82
|
+
private loaded;
|
|
83
|
+
private readonly statuses;
|
|
84
|
+
constructor(ctx: Context);
|
|
85
|
+
private get creds();
|
|
86
|
+
private file;
|
|
87
|
+
private load;
|
|
88
|
+
private ensureLocalHost;
|
|
89
|
+
private save;
|
|
90
|
+
list(): NodeProfile[];
|
|
91
|
+
get(id: string): NodeProfile | undefined;
|
|
92
|
+
hasSecret(id: string): Promise<{
|
|
93
|
+
hasPassword: boolean;
|
|
94
|
+
hasKey: boolean;
|
|
95
|
+
hasPassphrase: boolean;
|
|
96
|
+
}>;
|
|
97
|
+
getSecrets(id: string): Promise<NodeSecrets | undefined>;
|
|
98
|
+
create(input: NodeCreateInput): Promise<NodeProfile>;
|
|
99
|
+
update(id: string, input: NodeUpdateInput): Promise<NodeProfile>;
|
|
100
|
+
remove(id: string): Promise<void>;
|
|
101
|
+
status(id: string): NodeStatus;
|
|
102
|
+
setStatus(id: string, status: NodeStatus): void;
|
|
103
|
+
private credKey;
|
|
104
|
+
private writeSecrets;
|
|
105
|
+
}
|
|
106
|
+
//#endregion
|
|
107
|
+
export { CredentialLike, NodeCreateInput, NodeDocker, NodeJumpHop, NodeProfile, NodeRegistry, NodeSecrets, NodeSsh, NodeSshAuth, NodeStatus, NodeTestReport, NodeType, NodeUpdateInput, dshHome, slugifyName };
|
|
108
|
+
//# sourceMappingURL=node-registry.d.ts.map
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { chmodSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { createHash, randomBytes, timingSafeEqual } from "node:crypto";
|
|
4
|
+
import { credentialKey } from "@deepseek-ai/dsh-credentials";
|
|
5
|
+
//#region src/node-registry.ts
|
|
6
|
+
function newId() {
|
|
7
|
+
return `n${randomBytes(6).toString("hex")}`;
|
|
8
|
+
}
|
|
9
|
+
function slugifyName(name) {
|
|
10
|
+
return name.trim().slice(0, 64) || "node";
|
|
11
|
+
}
|
|
12
|
+
function isLowerHyphenatedId(value) {
|
|
13
|
+
return /^[a-z][a-z0-9-]*$/.test(value);
|
|
14
|
+
}
|
|
15
|
+
function sameName(a, b) {
|
|
16
|
+
const ha = createHash("sha256").update(a).digest();
|
|
17
|
+
const hb = createHash("sha256").update(b).digest();
|
|
18
|
+
return timingSafeEqual(ha, hb);
|
|
19
|
+
}
|
|
20
|
+
function dshHome() {
|
|
21
|
+
return process.env.DSH_HOME ?? join(homedir(), ".dsh");
|
|
22
|
+
}
|
|
23
|
+
function join(...parts) {
|
|
24
|
+
return parts.join("/");
|
|
25
|
+
}
|
|
26
|
+
const LOCAL_NODE_ID = "local";
|
|
27
|
+
var NodeRegistry = class {
|
|
28
|
+
ctx;
|
|
29
|
+
profiles = [];
|
|
30
|
+
loaded = false;
|
|
31
|
+
statuses = /* @__PURE__ */ new Map();
|
|
32
|
+
constructor(ctx) {
|
|
33
|
+
this.ctx = ctx;
|
|
34
|
+
}
|
|
35
|
+
get creds() {
|
|
36
|
+
return this.ctx.credentials;
|
|
37
|
+
}
|
|
38
|
+
file() {
|
|
39
|
+
return `${dshHome()}/dshb/nodes.json`;
|
|
40
|
+
}
|
|
41
|
+
load() {
|
|
42
|
+
if (this.loaded) return;
|
|
43
|
+
this.loaded = true;
|
|
44
|
+
try {
|
|
45
|
+
this.profiles = JSON.parse(readFileSync(this.file(), "utf8"));
|
|
46
|
+
} catch {
|
|
47
|
+
this.profiles = [];
|
|
48
|
+
}
|
|
49
|
+
this.ensureLocalHost();
|
|
50
|
+
}
|
|
51
|
+
ensureLocalHost() {
|
|
52
|
+
const existing = this.profiles.find((p) => p.type === "local-host");
|
|
53
|
+
if (existing) {
|
|
54
|
+
if (existing.name !== "默认环境") {
|
|
55
|
+
existing.name = "默认环境";
|
|
56
|
+
existing.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
57
|
+
this.save();
|
|
58
|
+
}
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
62
|
+
this.profiles.unshift({
|
|
63
|
+
id: LOCAL_NODE_ID,
|
|
64
|
+
name: "默认环境",
|
|
65
|
+
type: "local-host",
|
|
66
|
+
createdAt: now,
|
|
67
|
+
updatedAt: now
|
|
68
|
+
});
|
|
69
|
+
this.save();
|
|
70
|
+
}
|
|
71
|
+
save() {
|
|
72
|
+
const file = this.file();
|
|
73
|
+
const dir = file.slice(0, file.lastIndexOf("/"));
|
|
74
|
+
mkdirSync(dir, {
|
|
75
|
+
recursive: true,
|
|
76
|
+
mode: 448
|
|
77
|
+
});
|
|
78
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
79
|
+
writeFileSync(tmp, JSON.stringify(this.profiles, null, 2), { mode: 384 });
|
|
80
|
+
renameSync(tmp, file);
|
|
81
|
+
chmodSync(file, 384);
|
|
82
|
+
}
|
|
83
|
+
list() {
|
|
84
|
+
this.load();
|
|
85
|
+
return this.profiles.map((p) => ({ ...p }));
|
|
86
|
+
}
|
|
87
|
+
get(id) {
|
|
88
|
+
this.load();
|
|
89
|
+
const found = this.profiles.find((p) => p.id === id);
|
|
90
|
+
return found ? { ...found } : void 0;
|
|
91
|
+
}
|
|
92
|
+
async hasSecret(id) {
|
|
93
|
+
const key = this.credKey(id);
|
|
94
|
+
if (!key) return {
|
|
95
|
+
hasPassword: false,
|
|
96
|
+
hasKey: false,
|
|
97
|
+
hasPassphrase: false
|
|
98
|
+
};
|
|
99
|
+
const payload = (await this.creds.readRecord(key))?.payload;
|
|
100
|
+
return {
|
|
101
|
+
hasPassword: Boolean(payload?.password),
|
|
102
|
+
hasKey: Boolean(payload?.privateKey),
|
|
103
|
+
hasPassphrase: Boolean(payload?.passphrase)
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
async getSecrets(id) {
|
|
107
|
+
const key = this.credKey(id);
|
|
108
|
+
if (!key) return void 0;
|
|
109
|
+
return (await this.creds.readRecord(key))?.payload;
|
|
110
|
+
}
|
|
111
|
+
async create(input) {
|
|
112
|
+
this.load();
|
|
113
|
+
const name = slugifyName(input.name);
|
|
114
|
+
if (this.profiles.some((p) => sameName(p.name, name))) throw new Error(`node name "${name}" already exists`);
|
|
115
|
+
const id = newId();
|
|
116
|
+
if (!isLowerHyphenatedId(id)) throw new Error("internal: invalid generated id");
|
|
117
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
118
|
+
const profile = {
|
|
119
|
+
id,
|
|
120
|
+
name,
|
|
121
|
+
type: input.type,
|
|
122
|
+
ssh: input.ssh,
|
|
123
|
+
docker: input.docker,
|
|
124
|
+
createdAt: now,
|
|
125
|
+
updatedAt: now
|
|
126
|
+
};
|
|
127
|
+
if (input.secrets && (input.secrets.password || input.secrets.privateKey || input.secrets.passphrase)) await this.writeSecrets(id, input.secrets);
|
|
128
|
+
this.profiles.push(profile);
|
|
129
|
+
this.save();
|
|
130
|
+
return { ...profile };
|
|
131
|
+
}
|
|
132
|
+
async update(id, input) {
|
|
133
|
+
this.load();
|
|
134
|
+
const idx = this.profiles.findIndex((p) => p.id === id);
|
|
135
|
+
if (idx < 0) throw new Error(`node ${id} not found`);
|
|
136
|
+
const current = this.profiles[idx];
|
|
137
|
+
const next = {
|
|
138
|
+
...current,
|
|
139
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
140
|
+
};
|
|
141
|
+
if (input.name !== void 0) next.name = slugifyName(input.name);
|
|
142
|
+
if (input.ssh) next.ssh = {
|
|
143
|
+
...current.ssh,
|
|
144
|
+
...input.ssh
|
|
145
|
+
};
|
|
146
|
+
if (input.docker) next.docker = {
|
|
147
|
+
...current.docker,
|
|
148
|
+
...input.docker
|
|
149
|
+
};
|
|
150
|
+
this.profiles[idx] = next;
|
|
151
|
+
if (input.secrets && (input.secrets.password || input.secrets.privateKey || input.secrets.passphrase)) await this.writeSecrets(id, input.secrets);
|
|
152
|
+
this.save();
|
|
153
|
+
return { ...next };
|
|
154
|
+
}
|
|
155
|
+
async remove(id) {
|
|
156
|
+
if (id === LOCAL_NODE_ID) throw new Error("默认环境节点不可删除");
|
|
157
|
+
this.load();
|
|
158
|
+
const idx = this.profiles.findIndex((p) => p.id === id);
|
|
159
|
+
if (idx < 0) return;
|
|
160
|
+
this.profiles.splice(idx, 1);
|
|
161
|
+
this.statuses.delete(id);
|
|
162
|
+
this.save();
|
|
163
|
+
const key = this.credKey(id);
|
|
164
|
+
if (key) await this.creds.deleteRecord(key);
|
|
165
|
+
}
|
|
166
|
+
status(id) {
|
|
167
|
+
return this.statuses.get(id) ?? {};
|
|
168
|
+
}
|
|
169
|
+
setStatus(id, status) {
|
|
170
|
+
this.statuses.set(id, status);
|
|
171
|
+
}
|
|
172
|
+
credKey(id) {
|
|
173
|
+
try {
|
|
174
|
+
return credentialKey("dshb-core", id);
|
|
175
|
+
} catch {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
async writeSecrets(id, secrets) {
|
|
180
|
+
const key = this.credKey(id);
|
|
181
|
+
if (!key) throw new Error("credentials service unavailable");
|
|
182
|
+
await this.creds.modifyRecord(key, async () => ({
|
|
183
|
+
kind: "grant",
|
|
184
|
+
payload: { ...secrets }
|
|
185
|
+
}));
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
//#endregion
|
|
189
|
+
export { NodeRegistry, dshHome, slugifyName };
|
|
190
|
+
|
|
191
|
+
//# sourceMappingURL=node-registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node-registry.js","names":[],"sources":["../src/node-registry.ts"],"sourcesContent":["import { createHash, randomBytes, timingSafeEqual } from 'node:crypto'\nimport { chmodSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { credentialKey, type CredentialKey } from '@deepseek-ai/dsh-credentials'\nimport type { Context } from '@deepseek-ai/cordis'\n\nexport type NodeType = 'local-host' | 'local-docker' | 'remote-ssh' | 'remote-docker'\n\nexport interface NodeSshAuth {\n kind: 'password' | 'key' | 'agent'\n keyPath?: string\n}\n\nexport interface NodeJumpHop {\n host: string\n port?: number\n username?: string\n keyPath?: string\n}\n\nexport interface NodeSsh {\n host: string\n port: number\n username: string\n auth: NodeSshAuth\n jump?: NodeJumpHop[]\n hostKeyFingerprint?: string\n}\n\nexport interface NodeDocker {\n mode: 'existing' | 'managed'\n containerId?: string\n image?: string\n resources?: { cpus?: number; memoryMB?: number }\n}\n\nexport interface NodeProfile {\n id: string\n name: string\n type: NodeType\n ssh?: NodeSsh\n docker?: NodeDocker\n createdAt: string\n updatedAt: string\n}\n\nexport interface NodeSecrets {\n password?: string\n privateKey?: string\n passphrase?: string\n}\n\nexport interface NodeStatus {\n reachable?: boolean\n lastCheckedAt?: string\n error?: string\n}\n\nexport interface NodeTestReport {\n ok: boolean\n reachable?: boolean\n error?: string\n category?: string\n}\n\nexport interface NodeCreateInput {\n name: string\n type: NodeType\n ssh?: Omit<NodeSsh, 'hostKeyFingerprint'> & { hostKeyFingerprint?: string }\n docker?: NodeDocker\n secrets?: NodeSecrets\n}\n\nexport interface NodeUpdateInput {\n name?: string\n ssh?: Partial<Omit<NodeSsh, 'hostKeyFingerprint'>>\n docker?: Partial<NodeDocker>\n secrets?: NodeSecrets\n}\n\nexport interface CredentialLike {\n readRecord(key: CredentialKey): Promise<unknown>\n modifyRecord(key: CredentialKey, mutate: (current: unknown) => Promise<unknown>): Promise<unknown>\n deleteRecord(key: CredentialKey): Promise<void>\n}\n\nfunction newId(): string {\n return `n${randomBytes(6).toString('hex')}`\n}\n\nexport function slugifyName(name: string): string {\n return name.trim().slice(0, 64) || 'node'\n}\n\nfunction isLowerHyphenatedId(value: string): boolean {\n return /^[a-z][a-z0-9-]*$/.test(value)\n}\n\nfunction sameName(a: string, b: string): boolean {\n const ha = createHash('sha256').update(a).digest()\n const hb = createHash('sha256').update(b).digest()\n return timingSafeEqual(ha, hb)\n}\n\nexport function dshHome(): string {\n return process.env.DSH_HOME ?? join(homedir(), '.dsh')\n}\n\nfunction join(...parts: string[]): string {\n return parts.join('/')\n}\n\nconst LOCAL_NODE_ID = 'local'\n\nexport class NodeRegistry {\n private profiles: NodeProfile[] = []\n private loaded = false\n private readonly statuses = new Map<string, NodeStatus>()\n\n constructor(private readonly ctx: Context) {}\n\n private get creds(): CredentialLike {\n return this.ctx.credentials as unknown as CredentialLike\n }\n\n private file(): string {\n return `${dshHome()}/dshb/nodes.json`\n }\n\n private load(): void {\n if (this.loaded) return\n this.loaded = true\n try {\n this.profiles = JSON.parse(readFileSync(this.file(), 'utf8')) as NodeProfile[]\n } catch {\n this.profiles = []\n }\n this.ensureLocalHost()\n }\n\n private ensureLocalHost(): void {\n const existing = this.profiles.find((p) => p.type === 'local-host')\n if (existing) {\n if (existing.name !== '默认环境') {\n existing.name = '默认环境'\n existing.updatedAt = new Date().toISOString()\n this.save()\n }\n return\n }\n const now = new Date().toISOString()\n this.profiles.unshift({\n id: LOCAL_NODE_ID,\n name: '默认环境',\n type: 'local-host',\n createdAt: now,\n updatedAt: now,\n })\n this.save()\n }\n\n private save(): void {\n const file = this.file()\n const dir = file.slice(0, file.lastIndexOf('/'))\n mkdirSync(dir, { recursive: true, mode: 0o700 })\n const tmp = `${file}.${process.pid}.tmp`\n writeFileSync(tmp, JSON.stringify(this.profiles, null, 2), { mode: 0o600 })\n renameSync(tmp, file)\n chmodSync(file, 0o600)\n }\n\n list(): NodeProfile[] {\n this.load()\n return this.profiles.map((p) => ({ ...p }))\n }\n\n get(id: string): NodeProfile | undefined {\n this.load()\n const found = this.profiles.find((p) => p.id === id)\n return found ? { ...found } : undefined\n }\n\n async hasSecret(id: string): Promise<{ hasPassword: boolean; hasKey: boolean; hasPassphrase: boolean }> {\n const key = this.credKey(id)\n if (!key) return { hasPassword: false, hasKey: false, hasPassphrase: false }\n const record = (await this.creds.readRecord(key)) as { payload?: NodeSecrets } | undefined\n const payload = record?.payload\n return {\n hasPassword: Boolean(payload?.password),\n hasKey: Boolean(payload?.privateKey),\n hasPassphrase: Boolean(payload?.passphrase),\n }\n }\n\n async getSecrets(id: string): Promise<NodeSecrets | undefined> {\n const key = this.credKey(id)\n if (!key) return undefined\n const record = (await this.creds.readRecord(key)) as { payload?: NodeSecrets } | undefined\n return record?.payload\n }\n\n async create(input: NodeCreateInput): Promise<NodeProfile> {\n this.load()\n const name = slugifyName(input.name)\n if (this.profiles.some((p) => sameName(p.name, name))) {\n throw new Error(`node name \"${name}\" already exists`)\n }\n const id = newId()\n if (!isLowerHyphenatedId(id)) throw new Error('internal: invalid generated id')\n const now = new Date().toISOString()\n const profile: NodeProfile = {\n id,\n name,\n type: input.type,\n ssh: input.ssh as NodeSsh | undefined,\n docker: input.docker,\n createdAt: now,\n updatedAt: now,\n }\n if (input.secrets && (input.secrets.password || input.secrets.privateKey || input.secrets.passphrase)) {\n await this.writeSecrets(id, input.secrets)\n }\n this.profiles.push(profile)\n this.save()\n return { ...profile }\n }\n\n async update(id: string, input: NodeUpdateInput): Promise<NodeProfile> {\n this.load()\n const idx = this.profiles.findIndex((p) => p.id === id)\n if (idx < 0) throw new Error(`node ${id} not found`)\n const current = this.profiles[idx]\n const next: NodeProfile = { ...current, updatedAt: new Date().toISOString() }\n if (input.name !== undefined) next.name = slugifyName(input.name)\n if (input.ssh) next.ssh = { ...current.ssh, ...input.ssh } as NodeSsh\n if (input.docker) next.docker = { ...current.docker, ...input.docker } as NodeDocker\n this.profiles[idx] = next\n if (input.secrets && (input.secrets.password || input.secrets.privateKey || input.secrets.passphrase)) {\n await this.writeSecrets(id, input.secrets)\n }\n this.save()\n return { ...next }\n }\n\n async remove(id: string): Promise<void> {\n if (id === LOCAL_NODE_ID) throw new Error('默认环境节点不可删除')\n this.load()\n const idx = this.profiles.findIndex((p) => p.id === id)\n if (idx < 0) return\n this.profiles.splice(idx, 1)\n this.statuses.delete(id)\n this.save()\n const key = this.credKey(id)\n if (key) await this.creds.deleteRecord(key)\n }\n\n status(id: string): NodeStatus {\n return this.statuses.get(id) ?? {}\n }\n\n setStatus(id: string, status: NodeStatus): void {\n this.statuses.set(id, status)\n }\n\n private credKey(id: string): CredentialKey | undefined {\n try {\n return credentialKey('dshb-core', id)\n } catch {\n return undefined\n }\n }\n\n private async writeSecrets(id: string, secrets: NodeSecrets): Promise<void> {\n const key = this.credKey(id)\n if (!key) throw new Error('credentials service unavailable')\n await this.creds.modifyRecord(key, async () => ({\n kind: 'grant' as const,\n payload: { ...secrets },\n }))\n }\n}\n"],"mappings":"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"}
|
package/lib/routes.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { t as Context } from "./index-CLOUBKxU.js";
|
|
2
|
+
import { KnownHostsStore } from "./known-hosts.js";
|
|
3
|
+
import { NodeRegistry } from "./node-registry.js";
|
|
4
|
+
//#region src/routes.d.ts
|
|
5
|
+
declare function registerNodeRoutes(ctx: Context, registry: NodeRegistry): Promise<void>;
|
|
6
|
+
declare function registerSshConfigRoutes(ctx: Context, knownHosts: KnownHostsStore): Promise<void>;
|
|
7
|
+
//#endregion
|
|
8
|
+
export { registerNodeRoutes, registerSshConfigRoutes };
|
|
9
|
+
//# sourceMappingURL=routes.d.ts.map
|