@artenx/dshb 0.0.5 → 0.1.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/README.md +18 -0
- package/cordis.patch.yml +9 -9
- package/lib/auth/credentials.d.ts +30 -0
- package/lib/auth/credentials.js +121 -0
- package/lib/auth/credentials.js.map +1 -0
- package/lib/auth/decorate-webserver.d.ts +7 -0
- package/lib/auth/decorate-webserver.js +75 -0
- package/lib/auth/decorate-webserver.js.map +1 -0
- package/lib/auth/gate.d.ts +14 -0
- package/lib/auth/gate.js +45 -0
- package/lib/auth/gate.js.map +1 -0
- package/lib/auth/index.d.ts +8 -0
- package/lib/auth/index.js +51 -0
- package/lib/auth/index.js.map +1 -0
- package/lib/auth/login-page.d.ts +5 -0
- package/lib/auth/login-page.js +87 -0
- package/lib/auth/login-page.js.map +1 -0
- package/lib/auth/loopback-compat.d.ts +6 -0
- package/lib/auth/loopback-compat.js +94 -0
- package/lib/auth/loopback-compat.js.map +1 -0
- package/lib/auth/loopback.d.ts +7 -0
- package/lib/auth/loopback.js +24 -0
- package/lib/auth/loopback.js.map +1 -0
- package/lib/auth/ratelimit.d.ts +11 -0
- package/lib/auth/ratelimit.js +37 -0
- package/lib/auth/ratelimit.js.map +1 -0
- package/lib/auth/routes.d.ts +8 -0
- package/lib/auth/routes.js +224 -0
- package/lib/auth/routes.js.map +1 -0
- package/lib/auth/session.d.ts +11 -0
- package/lib/auth/session.js +52 -0
- package/lib/auth/session.js.map +1 -0
- package/lib/auth/startup.d.ts +15 -0
- package/lib/auth/startup.js +68 -0
- package/lib/auth/startup.js.map +1 -0
- package/lib/auth/webserver.d.ts +18 -0
- package/lib/auth/webserver.js +60 -0
- package/lib/auth/webserver.js.map +1 -0
- package/lib/client.js +2770 -0
- package/lib/client.js.map +1 -0
- package/lib/connection-sIX25pkS.d.ts +46 -0
- package/lib/core/audit.d.ts +16 -0
- package/lib/core/audit.js +38 -0
- package/lib/core/audit.js.map +1 -0
- package/lib/core/index.d.ts +8 -0
- package/lib/core/index.js +77 -0
- package/lib/core/index.js.map +1 -0
- package/lib/core/known-hosts.d.ts +23 -0
- package/lib/core/known-hosts.js +76 -0
- package/lib/core/known-hosts.js.map +1 -0
- package/lib/core/node-registry.d.ts +108 -0
- package/lib/core/node-registry.js +191 -0
- package/lib/core/node-registry.js.map +1 -0
- package/lib/core/routes.d.ts +9 -0
- package/lib/core/routes.js +404 -0
- package/lib/core/routes.js.map +1 -0
- package/lib/core/ssh-config.d.ts +23 -0
- package/lib/core/ssh-config.js +70 -0
- package/lib/core/ssh-config.js.map +1 -0
- package/lib/core/test.d.ts +43 -0
- package/lib/core/test.js +116 -0
- package/lib/core/test.js.map +1 -0
- package/lib/core/workspace-bindings.d.ts +28 -0
- package/lib/core/workspace-bindings.js +86 -0
- package/lib/core/workspace-bindings.js.map +1 -0
- package/lib/core/workspaces.d.ts +9 -0
- package/lib/core/workspaces.js +283 -0
- package/lib/core/workspaces.js.map +1 -0
- package/lib/exec-docker/daemon-runner.d.ts +21 -0
- package/lib/exec-docker/daemon-runner.js +326 -0
- package/lib/exec-docker/daemon-runner.js.map +1 -0
- package/lib/exec-docker/docker-backend.d.ts +48 -0
- package/lib/exec-docker/docker-backend.js +1 -0
- package/lib/exec-docker/docker-client.d.ts +46 -0
- package/lib/exec-docker/docker-client.js +302 -0
- package/lib/exec-docker/docker-client.js.map +1 -0
- package/lib/exec-docker/docker-routes.d.ts +6 -0
- package/lib/exec-docker/docker-routes.js +215 -0
- package/lib/exec-docker/docker-routes.js.map +1 -0
- package/lib/exec-docker/fs-provider.d.ts +50 -0
- package/lib/exec-docker/fs-provider.js +92 -0
- package/lib/exec-docker/fs-provider.js.map +1 -0
- package/lib/exec-docker/host-runner.d.ts +29 -0
- package/lib/exec-docker/host-runner.js +17 -0
- package/lib/exec-docker/host-runner.js.map +1 -0
- package/lib/exec-docker/index.d.ts +21 -0
- package/lib/exec-docker/index.js +137 -0
- package/lib/exec-docker/index.js.map +1 -0
- package/lib/exec-docker/process-provider.d.ts +20 -0
- package/lib/exec-docker/process-provider.js +75 -0
- package/lib/exec-docker/process-provider.js.map +1 -0
- package/lib/exec-docker/provision.d.ts +24 -0
- package/lib/exec-docker/provision.js +103 -0
- package/lib/exec-docker/provision.js.map +1 -0
- package/lib/exec-docker/remote-docker-cli.d.ts +52 -0
- package/lib/exec-docker/remote-docker-cli.js +155 -0
- package/lib/exec-docker/remote-docker-cli.js.map +1 -0
- package/lib/exec-docker/world.d.ts +37 -0
- package/lib/exec-docker/world.js +62 -0
- package/lib/exec-docker/world.js.map +1 -0
- package/lib/exec-ssh/connection-pool.d.ts +25 -0
- package/lib/exec-ssh/connection-pool.js +54 -0
- package/lib/exec-ssh/connection-pool.js.map +1 -0
- package/lib/exec-ssh/connection.d.ts +2 -0
- package/lib/exec-ssh/connection.js +125 -0
- package/lib/exec-ssh/connection.js.map +1 -0
- package/lib/exec-ssh/env-scrub.d.ts +7 -0
- package/lib/exec-ssh/env-scrub.js +40 -0
- package/lib/exec-ssh/env-scrub.js.map +1 -0
- package/lib/exec-ssh/executor.d.ts +46 -0
- package/lib/exec-ssh/executor.js +212 -0
- package/lib/exec-ssh/executor.js.map +1 -0
- package/lib/exec-ssh/fs-provider.d.ts +50 -0
- package/lib/exec-ssh/fs-provider.js +92 -0
- package/lib/exec-ssh/fs-provider.js.map +1 -0
- package/lib/exec-ssh/index.d.ts +16 -0
- package/lib/exec-ssh/index.js +30 -0
- package/lib/exec-ssh/index.js.map +1 -0
- package/lib/exec-ssh/process-provider.d.ts +20 -0
- package/lib/exec-ssh/process-provider.js +75 -0
- package/lib/exec-ssh/process-provider.js.map +1 -0
- package/lib/exec-ssh/runner.d.ts +13 -0
- package/lib/exec-ssh/runner.js +62 -0
- package/lib/exec-ssh/runner.js.map +1 -0
- package/lib/exec-ssh/world.d.ts +79 -0
- package/lib/exec-ssh/world.js +95 -0
- package/lib/exec-ssh/world.js.map +1 -0
- package/lib/index.d.ts +2 -0
- package/lib/index.js +2 -0
- package/lib/router/decorate-fs.d.ts +6 -0
- package/lib/router/decorate-fs.js +78 -0
- package/lib/router/decorate-fs.js.map +1 -0
- package/lib/router/decorate-shell.d.ts +6 -0
- package/lib/router/decorate-shell.js +35 -0
- package/lib/router/decorate-shell.js.map +1 -0
- package/lib/router/decorate-subprocess.d.ts +6 -0
- package/lib/router/decorate-subprocess.js +35 -0
- package/lib/router/decorate-subprocess.js.map +1 -0
- package/lib/router/index.d.ts +10 -0
- package/lib/router/index.js +21 -0
- package/lib/router/index.js.map +1 -0
- package/lib/router/resolve.d.ts +22 -0
- package/lib/router/resolve.js +45 -0
- package/lib/router/resolve.js.map +1 -0
- package/lib/router/router-fs.d.ts +25 -0
- package/lib/router/router-fs.js +64 -0
- package/lib/router/router-fs.js.map +1 -0
- package/lib/router/router-shell.d.ts +14 -0
- package/lib/router/router-shell.js +36 -0
- package/lib/router/router-shell.js.map +1 -0
- package/lib/router/router-subprocess.d.ts +13 -0
- package/lib/router/router-subprocess.js +33 -0
- package/lib/router/router-subprocess.js.map +1 -0
- package/lib/router/types.d.ts +2 -0
- package/lib/router/types.js +1 -0
- package/lib/types-C0TtEO7z.d.ts +67 -0
- package/lib/ui/decorate-picker.d.ts +6 -0
- package/lib/ui/decorate-picker.js +58 -0
- package/lib/ui/decorate-picker.js.map +1 -0
- package/lib/ui/index.d.ts +8 -0
- package/lib/ui/index.js +11 -0
- package/lib/ui/index.js.map +1 -0
- package/lib/ui/picker.d.ts +10 -0
- package/lib/ui/picker.js +58 -0
- package/lib/ui/picker.js.map +1 -0
- package/package.json +146 -13
- package/LICENSE +0 -21
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
//#region src/exec-docker/remote-docker-cli.ts
|
|
2
|
+
var RemoteDockerCli = class {
|
|
3
|
+
ssh;
|
|
4
|
+
containerId;
|
|
5
|
+
constructor(ssh, containerId) {
|
|
6
|
+
this.ssh = ssh;
|
|
7
|
+
this.containerId = containerId;
|
|
8
|
+
}
|
|
9
|
+
async exec(argv, cwd, env = {}, stdinData) {
|
|
10
|
+
const envFlags = Object.entries(env).map(([k, v]) => ["-e", `${k}=${v}`]).flat();
|
|
11
|
+
return this.ssh.exec([
|
|
12
|
+
"docker",
|
|
13
|
+
"exec",
|
|
14
|
+
"-w",
|
|
15
|
+
cwd,
|
|
16
|
+
...envFlags,
|
|
17
|
+
this.containerId,
|
|
18
|
+
...argv
|
|
19
|
+
], "/", {}, stdinData);
|
|
20
|
+
}
|
|
21
|
+
async execShell(command, cwd) {
|
|
22
|
+
return this.ssh.exec([
|
|
23
|
+
"docker",
|
|
24
|
+
"exec",
|
|
25
|
+
"-w",
|
|
26
|
+
cwd,
|
|
27
|
+
this.containerId,
|
|
28
|
+
"bash",
|
|
29
|
+
"-c",
|
|
30
|
+
command
|
|
31
|
+
], "/");
|
|
32
|
+
}
|
|
33
|
+
async readFile(path) {
|
|
34
|
+
const result = await this.ssh.exec([
|
|
35
|
+
"docker",
|
|
36
|
+
"exec",
|
|
37
|
+
this.containerId,
|
|
38
|
+
"cat",
|
|
39
|
+
path
|
|
40
|
+
], "/");
|
|
41
|
+
return Buffer.from(result.stdout, "utf8");
|
|
42
|
+
}
|
|
43
|
+
async writeFile(path, content) {
|
|
44
|
+
const b64 = (Buffer.isBuffer(content) ? content : Buffer.from(content, "utf8")).toString("base64");
|
|
45
|
+
await this.ssh.exec([
|
|
46
|
+
"docker",
|
|
47
|
+
"exec",
|
|
48
|
+
this.containerId,
|
|
49
|
+
"bash",
|
|
50
|
+
"-c",
|
|
51
|
+
`printf '%s' '${b64}' | base64 -d > '${path.replace(/'/g, "'\\''")}'`
|
|
52
|
+
], "/");
|
|
53
|
+
}
|
|
54
|
+
async listDir(path) {
|
|
55
|
+
return (await this.ssh.exec([
|
|
56
|
+
"docker",
|
|
57
|
+
"exec",
|
|
58
|
+
this.containerId,
|
|
59
|
+
"ls",
|
|
60
|
+
"-1pa",
|
|
61
|
+
"--time-style=+%s",
|
|
62
|
+
path
|
|
63
|
+
], "/")).stdout.split("\n").filter((line) => line.trim()).map((line) => {
|
|
64
|
+
const name = line.replace(/[/@*]$/, "").trim();
|
|
65
|
+
if (!name) return null;
|
|
66
|
+
const isDir = line.endsWith("/");
|
|
67
|
+
const isSymlink = line.endsWith("@");
|
|
68
|
+
return {
|
|
69
|
+
name,
|
|
70
|
+
isDir,
|
|
71
|
+
isFile: !isDir && !isSymlink,
|
|
72
|
+
isSymlink,
|
|
73
|
+
size: 0,
|
|
74
|
+
mtime: 0
|
|
75
|
+
};
|
|
76
|
+
}).filter((e) => e !== null);
|
|
77
|
+
}
|
|
78
|
+
async stat(path) {
|
|
79
|
+
const line = (await this.ssh.exec([
|
|
80
|
+
"docker",
|
|
81
|
+
"exec",
|
|
82
|
+
this.containerId,
|
|
83
|
+
"stat",
|
|
84
|
+
"-c",
|
|
85
|
+
"%s %Y %F",
|
|
86
|
+
path
|
|
87
|
+
], "/")).stdout.trim();
|
|
88
|
+
if (!line) return void 0;
|
|
89
|
+
const parts = line.split(/\s+/);
|
|
90
|
+
if (parts.length < 3) return void 0;
|
|
91
|
+
const size = Number(parts[0]);
|
|
92
|
+
const mtime = Number(parts[1]) * 1e3;
|
|
93
|
+
const ftype = parts.slice(2).join(" ");
|
|
94
|
+
return {
|
|
95
|
+
size,
|
|
96
|
+
mtime,
|
|
97
|
+
isDirectory: ftype.includes("directory"),
|
|
98
|
+
isFile: ftype.includes("regular")
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
async mkdir(path) {
|
|
102
|
+
await this.ssh.exec([
|
|
103
|
+
"docker",
|
|
104
|
+
"exec",
|
|
105
|
+
this.containerId,
|
|
106
|
+
"mkdir",
|
|
107
|
+
"-p",
|
|
108
|
+
path
|
|
109
|
+
], "/");
|
|
110
|
+
}
|
|
111
|
+
async remove(path) {
|
|
112
|
+
await this.ssh.exec([
|
|
113
|
+
"docker",
|
|
114
|
+
"exec",
|
|
115
|
+
this.containerId,
|
|
116
|
+
"rm",
|
|
117
|
+
"-f",
|
|
118
|
+
path
|
|
119
|
+
], "/");
|
|
120
|
+
}
|
|
121
|
+
async rename(src, dest) {
|
|
122
|
+
await this.ssh.exec([
|
|
123
|
+
"docker",
|
|
124
|
+
"exec",
|
|
125
|
+
this.containerId,
|
|
126
|
+
"mv",
|
|
127
|
+
src,
|
|
128
|
+
dest
|
|
129
|
+
], "/");
|
|
130
|
+
}
|
|
131
|
+
async ensureRg() {
|
|
132
|
+
if ((await this.ssh.exec([
|
|
133
|
+
"docker",
|
|
134
|
+
"exec",
|
|
135
|
+
this.containerId,
|
|
136
|
+
"bash",
|
|
137
|
+
"-c",
|
|
138
|
+
"command -v rg || echo __missing__"
|
|
139
|
+
], "/")).stdout.includes("__missing__")) await this.ssh.exec([
|
|
140
|
+
"docker",
|
|
141
|
+
"exec",
|
|
142
|
+
this.containerId,
|
|
143
|
+
"bash",
|
|
144
|
+
"-c",
|
|
145
|
+
"apk add --no-cache ripgrep 2>/dev/null || apt-get install -y ripgrep 2>/dev/null || true"
|
|
146
|
+
], "/");
|
|
147
|
+
}
|
|
148
|
+
async pty(argv, cwd, cols, rows) {
|
|
149
|
+
throw new Error("remote docker PTY not yet supported");
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
//#endregion
|
|
153
|
+
export { RemoteDockerCli };
|
|
154
|
+
|
|
155
|
+
//# sourceMappingURL=remote-docker-cli.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"remote-docker-cli.js","names":[],"sources":["../../src/exec-docker/remote-docker-cli.ts"],"sourcesContent":["import type { DockerBackend } from './docker-backend.js'\n\ninterface SshExecutorLike {\n exec(argv: string[], cwd: string, env?: Record<string, string>, stdinData?: Buffer): Promise<{ code: number; stdout: string; stderr: string }>\n}\n\nexport class RemoteDockerCli implements DockerBackend {\n constructor(\n private readonly ssh: SshExecutorLike,\n private readonly containerId: string,\n ) {}\n\n async exec(argv: string[], cwd: string, env: Record<string, string> = {}, stdinData?: Buffer): Promise<{ code: number; stdout: string; stderr: string }> {\n const envFlags = Object.entries(env).map(([k, v]) => ['-e', `${k}=${v}`]).flat()\n return this.ssh.exec(['docker', 'exec', '-w', cwd, ...envFlags, this.containerId, ...argv], '/', {}, stdinData)\n }\n\n async execShell(command: string, cwd: string): Promise<{ code: number; stdout: string; stderr: string }> {\n return this.ssh.exec(['docker', 'exec', '-w', cwd, this.containerId, 'bash', '-c', command], '/')\n }\n\n async readFile(path: string): Promise<Buffer> {\n const result = await this.ssh.exec(['docker', 'exec', this.containerId, 'cat', path], '/')\n return Buffer.from(result.stdout, 'utf8')\n }\n\n async writeFile(path: string, content: Buffer | string): Promise<void> {\n const data = Buffer.isBuffer(content) ? content : Buffer.from(content, 'utf8')\n const b64 = data.toString('base64')\n await this.ssh.exec(['docker', 'exec', this.containerId, 'bash', '-c', `printf '%s' '${b64}' | base64 -d > '${path.replace(/'/g, \"'\\\\''\")}'`], '/')\n }\n\n async listDir(path: string): Promise<Array<{ name: string; isDir: boolean; isFile: boolean; isSymlink: boolean; size: number; mtime: number }>> {\n const result = await this.ssh.exec(['docker', 'exec', this.containerId, 'ls', '-1pa', '--time-style=+%s', path], '/')\n return result.stdout\n .split('\\n')\n .filter((line) => line.trim())\n .map((line) => {\n const name = line.replace(/[/@*]$/, '').trim()\n if (!name) return null\n const isDir = line.endsWith('/')\n const isSymlink = line.endsWith('@')\n return { name, isDir, isFile: !isDir && !isSymlink, isSymlink, size: 0, mtime: 0 }\n })\n .filter((e): e is NonNullable<typeof e> => e !== null)\n }\n\n async stat(path: string): Promise<{ size: number; mtime: number; isDirectory: boolean; isFile: boolean } | undefined> {\n const result = await this.ssh.exec(['docker', 'exec', this.containerId, 'stat', '-c', '%s %Y %F', path], '/')\n const line = result.stdout.trim()\n if (!line) return undefined\n const parts = line.split(/\\s+/)\n if (parts.length < 3) return undefined\n const size = Number(parts[0])\n const mtime = Number(parts[1]) * 1000\n const ftype = parts.slice(2).join(' ')\n return { size, mtime, isDirectory: ftype.includes('directory'), isFile: ftype.includes('regular') }\n }\n\n async mkdir(path: string): Promise<void> {\n await this.ssh.exec(['docker', 'exec', this.containerId, 'mkdir', '-p', path], '/')\n }\n\n async remove(path: string): Promise<void> {\n await this.ssh.exec(['docker', 'exec', this.containerId, 'rm', '-f', path], '/')\n }\n\n async rename(src: string, dest: string): Promise<void> {\n await this.ssh.exec(['docker', 'exec', this.containerId, 'mv', src, dest], '/')\n }\n\n async ensureRg(): Promise<void> {\n const result = await this.ssh.exec(['docker', 'exec', this.containerId, 'bash', '-c', 'command -v rg || echo __missing__'], '/')\n if (result.stdout.includes('__missing__')) {\n await this.ssh.exec(['docker', 'exec', this.containerId, 'bash', '-c', 'apk add --no-cache ripgrep 2>/dev/null || apt-get install -y ripgrep 2>/dev/null || true'], '/')\n }\n }\n\n async pty(argv: string[], cwd: string, cols: number, rows: number): Promise<{ stream: NodeJS.ReadWriteStream; resize: (c: number, r: number) => void; kill: () => void }> {\n throw new Error('remote docker PTY not yet supported')\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { DockerBackend } from "./docker-backend.js";
|
|
2
|
+
import { DockerFileSystem } from "./fs-provider.js";
|
|
3
|
+
import { a as NodeTestReport, t as ExecutionWorldProvider } from "../types-C0TtEO7z.js";
|
|
4
|
+
import { DockerShellExecutor, DockerSubprocessRuntime } from "./process-provider.js";
|
|
5
|
+
//#region src/exec-docker/world.d.ts
|
|
6
|
+
interface DockerNodeConfig {
|
|
7
|
+
nodeId: string;
|
|
8
|
+
containerId: string;
|
|
9
|
+
}
|
|
10
|
+
interface SshExecutorLike {
|
|
11
|
+
exec(argv: string[], cwd: string, env?: Record<string, string>, stdinData?: Buffer): Promise<{
|
|
12
|
+
code: number;
|
|
13
|
+
stdout: string;
|
|
14
|
+
stderr: string;
|
|
15
|
+
}>;
|
|
16
|
+
}
|
|
17
|
+
declare class DockerExecutionWorld implements ExecutionWorldProvider {
|
|
18
|
+
readonly nodeId: string;
|
|
19
|
+
readonly fs: DockerFileSystem;
|
|
20
|
+
readonly subprocess: DockerSubprocessRuntime;
|
|
21
|
+
readonly shell: DockerShellExecutor;
|
|
22
|
+
readonly backend: DockerBackend;
|
|
23
|
+
constructor(nodeId: string, backend: DockerBackend);
|
|
24
|
+
static local(nodeId: string, containerId: string): DockerExecutionWorld;
|
|
25
|
+
static remote(nodeId: string, containerId: string, sshExecutor: SshExecutorLike): DockerExecutionWorld;
|
|
26
|
+
ensureDir(remotePath: string): Promise<void>;
|
|
27
|
+
testConnection(): Promise<NodeTestReport>;
|
|
28
|
+
}
|
|
29
|
+
declare class DockerWorldRegistry {
|
|
30
|
+
private readonly worlds;
|
|
31
|
+
get(nodeId: string): DockerExecutionWorld | undefined;
|
|
32
|
+
ensure(nodeId: string, backend: DockerBackend): DockerExecutionWorld;
|
|
33
|
+
remove(nodeId: string): void;
|
|
34
|
+
}
|
|
35
|
+
//#endregion
|
|
36
|
+
export { DockerExecutionWorld, DockerNodeConfig, DockerWorldRegistry };
|
|
37
|
+
//# sourceMappingURL=world.d.ts.map
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { DockerClient } from "./docker-client.js";
|
|
2
|
+
import { RemoteDockerCli } from "./remote-docker-cli.js";
|
|
3
|
+
import { DockerFileSystem } from "./fs-provider.js";
|
|
4
|
+
import { DockerShellExecutor, DockerSubprocessRuntime } from "./process-provider.js";
|
|
5
|
+
//#region src/exec-docker/world.ts
|
|
6
|
+
var DockerExecutionWorld = class DockerExecutionWorld {
|
|
7
|
+
nodeId;
|
|
8
|
+
fs;
|
|
9
|
+
subprocess;
|
|
10
|
+
shell;
|
|
11
|
+
backend;
|
|
12
|
+
constructor(nodeId, backend) {
|
|
13
|
+
this.nodeId = nodeId;
|
|
14
|
+
this.backend = backend;
|
|
15
|
+
this.fs = new DockerFileSystem(backend);
|
|
16
|
+
this.subprocess = new DockerSubprocessRuntime(backend);
|
|
17
|
+
this.shell = new DockerShellExecutor(backend);
|
|
18
|
+
}
|
|
19
|
+
static local(nodeId, containerId) {
|
|
20
|
+
return new DockerExecutionWorld(nodeId, new DockerClient(containerId));
|
|
21
|
+
}
|
|
22
|
+
static remote(nodeId, containerId, sshExecutor) {
|
|
23
|
+
return new DockerExecutionWorld(nodeId, new RemoteDockerCli(sshExecutor, containerId));
|
|
24
|
+
}
|
|
25
|
+
async ensureDir(remotePath) {
|
|
26
|
+
await this.backend.mkdir(remotePath);
|
|
27
|
+
}
|
|
28
|
+
async testConnection() {
|
|
29
|
+
try {
|
|
30
|
+
return {
|
|
31
|
+
ok: (await this.backend.exec(["echo", "ok"], "/")).stdout.trim() === "ok",
|
|
32
|
+
reachable: true
|
|
33
|
+
};
|
|
34
|
+
} catch (err) {
|
|
35
|
+
return {
|
|
36
|
+
ok: false,
|
|
37
|
+
reachable: false,
|
|
38
|
+
error: err instanceof Error ? err.message : String(err)
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
var DockerWorldRegistry = class {
|
|
44
|
+
worlds = /* @__PURE__ */ new Map();
|
|
45
|
+
get(nodeId) {
|
|
46
|
+
return this.worlds.get(nodeId);
|
|
47
|
+
}
|
|
48
|
+
ensure(nodeId, backend) {
|
|
49
|
+
const existing = this.worlds.get(nodeId);
|
|
50
|
+
if (existing) return existing;
|
|
51
|
+
const world = new DockerExecutionWorld(nodeId, backend);
|
|
52
|
+
this.worlds.set(nodeId, world);
|
|
53
|
+
return world;
|
|
54
|
+
}
|
|
55
|
+
remove(nodeId) {
|
|
56
|
+
this.worlds.delete(nodeId);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
//#endregion
|
|
60
|
+
export { DockerExecutionWorld, DockerWorldRegistry };
|
|
61
|
+
|
|
62
|
+
//# sourceMappingURL=world.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"world.js","names":[],"sources":["../../src/exec-docker/world.ts"],"sourcesContent":["import type { ExecutionWorldProvider, NodeTestReport } from '../router/types.js'\nimport type { DockerBackend } from './docker-backend.js'\nimport { DockerClient } from './docker-client.js'\nimport { RemoteDockerCli } from './remote-docker-cli.js'\nimport { DockerFileSystem } from './fs-provider.js'\nimport { DockerShellExecutor, DockerSubprocessRuntime } from './process-provider.js'\n\nexport interface DockerNodeConfig {\n nodeId: string\n containerId: string\n}\n\ninterface SshExecutorLike {\n exec(argv: string[], cwd: string, env?: Record<string, string>, stdinData?: Buffer): Promise<{ code: number; stdout: string; stderr: string }>\n}\n\nexport class DockerExecutionWorld implements ExecutionWorldProvider {\n readonly fs: DockerFileSystem\n readonly subprocess: DockerSubprocessRuntime\n readonly shell: DockerShellExecutor\n readonly backend: DockerBackend\n\n constructor(readonly nodeId: string, backend: DockerBackend) {\n this.backend = backend\n this.fs = new DockerFileSystem(backend)\n this.subprocess = new DockerSubprocessRuntime(backend)\n this.shell = new DockerShellExecutor(backend)\n }\n\n static local(nodeId: string, containerId: string): DockerExecutionWorld {\n return new DockerExecutionWorld(nodeId, new DockerClient(containerId))\n }\n\n static remote(nodeId: string, containerId: string, sshExecutor: SshExecutorLike): DockerExecutionWorld {\n return new DockerExecutionWorld(nodeId, new RemoteDockerCli(sshExecutor, containerId))\n }\n\n async ensureDir(remotePath: string): Promise<void> {\n await this.backend.mkdir(remotePath)\n }\n\n async testConnection(): Promise<NodeTestReport> {\n try {\n const result = await this.backend.exec(['echo', 'ok'], '/')\n return { ok: result.stdout.trim() === 'ok', reachable: true }\n } catch (err) {\n return { ok: false, reachable: false, error: err instanceof Error ? err.message : String(err) }\n }\n }\n}\n\nexport class DockerWorldRegistry {\n private readonly worlds = new Map<string, DockerExecutionWorld>()\n\n get(nodeId: string): DockerExecutionWorld | undefined {\n return this.worlds.get(nodeId)\n }\n\n ensure(nodeId: string, backend: DockerBackend): DockerExecutionWorld {\n const existing = this.worlds.get(nodeId)\n if (existing) return existing\n const world = new DockerExecutionWorld(nodeId, backend)\n this.worlds.set(nodeId, world)\n return world\n }\n\n remove(nodeId: string): void {\n this.worlds.delete(nodeId)\n }\n}\n"],"mappings":";;;;;AAgBA,IAAa,uBAAb,MAAa,qBAAuD;CAM7C;CALrB;CACA;CACA;CACA;CAEA,YAAY,QAAyB,SAAwB;EAAxC,KAAA,SAAA;EACnB,KAAK,UAAU;EACf,KAAK,KAAK,IAAI,iBAAiB,OAAO;EACtC,KAAK,aAAa,IAAI,wBAAwB,OAAO;EACrD,KAAK,QAAQ,IAAI,oBAAoB,OAAO;CAC9C;CAEA,OAAO,MAAM,QAAgB,aAA2C;EACtE,OAAO,IAAI,qBAAqB,QAAQ,IAAI,aAAa,WAAW,CAAC;CACvE;CAEA,OAAO,OAAO,QAAgB,aAAqB,aAAoD;EACrG,OAAO,IAAI,qBAAqB,QAAQ,IAAI,gBAAgB,aAAa,WAAW,CAAC;CACvF;CAEA,MAAM,UAAU,YAAmC;EACjD,MAAM,KAAK,QAAQ,MAAM,UAAU;CACrC;CAEA,MAAM,iBAA0C;EAC9C,IAAI;GAEF,OAAO;IAAE,KAAI,MADQ,KAAK,QAAQ,KAAK,CAAC,QAAQ,IAAI,GAAG,GAAG,EAAA,CACtC,OAAO,KAAK,MAAM;IAAM,WAAW;GAAK;EAC9D,SAAS,KAAK;GACZ,OAAO;IAAE,IAAI;IAAO,WAAW;IAAO,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;GAAE;EAChG;CACF;AACF;AAEA,IAAa,sBAAb,MAAiC;CAC/B,yBAA0B,IAAI,IAAkC;CAEhE,IAAI,QAAkD;EACpD,OAAO,KAAK,OAAO,IAAI,MAAM;CAC/B;CAEA,OAAO,QAAgB,SAA8C;EACnE,MAAM,WAAW,KAAK,OAAO,IAAI,MAAM;EACvC,IAAI,UAAU,OAAO;EACrB,MAAM,QAAQ,IAAI,qBAAqB,QAAQ,OAAO;EACtD,KAAK,OAAO,IAAI,QAAQ,KAAK;EAC7B,OAAO;CACT;CAEA,OAAO,QAAsB;EAC3B,KAAK,OAAO,OAAO,MAAM;CAC3B;AACF"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { n as SshConnection, o as SshSecrets, s as SshTarget } from "../connection-sIX25pkS.js";
|
|
2
|
+
import { Context } from "@deepseek-ai/cordis";
|
|
3
|
+
//#region src/exec-ssh/connection-pool.d.ts
|
|
4
|
+
interface PooledConnection {
|
|
5
|
+
target: SshTarget;
|
|
6
|
+
secrets: SshSecrets;
|
|
7
|
+
connection: SshConnection;
|
|
8
|
+
}
|
|
9
|
+
declare class SshConnectionPool {
|
|
10
|
+
private readonly ctx;
|
|
11
|
+
private readonly pool;
|
|
12
|
+
constructor(ctx: Context);
|
|
13
|
+
private key;
|
|
14
|
+
acquire(target: SshTarget, secrets: SshSecrets): SshConnection;
|
|
15
|
+
test(target: SshTarget, secrets: SshSecrets): Promise<{
|
|
16
|
+
ok: boolean;
|
|
17
|
+
error?: string;
|
|
18
|
+
category?: string;
|
|
19
|
+
}>;
|
|
20
|
+
release(target: SshTarget): void;
|
|
21
|
+
closeAll(): void;
|
|
22
|
+
}
|
|
23
|
+
//#endregion
|
|
24
|
+
export { PooledConnection, SshConnectionPool };
|
|
25
|
+
//# sourceMappingURL=connection-pool.d.ts.map
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { SshConnection, toConnectionError } from "./connection.js";
|
|
2
|
+
//#region src/exec-ssh/connection-pool.ts
|
|
3
|
+
var SshConnectionPool = class {
|
|
4
|
+
ctx;
|
|
5
|
+
pool = /* @__PURE__ */ new Map();
|
|
6
|
+
constructor(ctx) {
|
|
7
|
+
this.ctx = ctx;
|
|
8
|
+
}
|
|
9
|
+
key(target) {
|
|
10
|
+
return `${target.host}:${target.port}:${target.username}`;
|
|
11
|
+
}
|
|
12
|
+
acquire(target, secrets) {
|
|
13
|
+
const k = this.key(target);
|
|
14
|
+
const existing = this.pool.get(k);
|
|
15
|
+
if (existing) return existing.connection;
|
|
16
|
+
const connection = new SshConnection(target, secrets);
|
|
17
|
+
this.pool.set(k, {
|
|
18
|
+
target,
|
|
19
|
+
secrets,
|
|
20
|
+
connection
|
|
21
|
+
});
|
|
22
|
+
return connection;
|
|
23
|
+
}
|
|
24
|
+
async test(target, secrets) {
|
|
25
|
+
try {
|
|
26
|
+
const conn = new SshConnection(target, secrets);
|
|
27
|
+
await conn.getClient();
|
|
28
|
+
conn.close();
|
|
29
|
+
return { ok: true };
|
|
30
|
+
} catch (err) {
|
|
31
|
+
const e = toConnectionError(err);
|
|
32
|
+
return {
|
|
33
|
+
ok: false,
|
|
34
|
+
error: e.message,
|
|
35
|
+
category: e.category
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
release(target) {
|
|
40
|
+
const k = this.key(target);
|
|
41
|
+
const entry = this.pool.get(k);
|
|
42
|
+
if (!entry) return;
|
|
43
|
+
entry.connection.close();
|
|
44
|
+
this.pool.delete(k);
|
|
45
|
+
}
|
|
46
|
+
closeAll() {
|
|
47
|
+
for (const entry of this.pool.values()) entry.connection.close();
|
|
48
|
+
this.pool.clear();
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
//#endregion
|
|
52
|
+
export { SshConnectionPool };
|
|
53
|
+
|
|
54
|
+
//# sourceMappingURL=connection-pool.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connection-pool.js","names":[],"sources":["../../src/exec-ssh/connection-pool.ts"],"sourcesContent":["import type { Context } from '@deepseek-ai/cordis'\nimport { SshConnection, type SshSecrets, type SshTarget, toConnectionError } from './connection.js'\n\nexport interface PooledConnection {\n target: SshTarget\n secrets: SshSecrets\n connection: SshConnection\n}\n\nexport class SshConnectionPool {\n private readonly pool = new Map<string, PooledConnection>()\n\n constructor(private readonly ctx: Context) {}\n\n private key(target: SshTarget): string {\n return `${target.host}:${target.port}:${target.username}`\n }\n\n acquire(target: SshTarget, secrets: SshSecrets): SshConnection {\n const k = this.key(target)\n const existing = this.pool.get(k)\n if (existing) return existing.connection\n const connection = new SshConnection(target, secrets)\n this.pool.set(k, { target, secrets, connection })\n return connection\n }\n\n async test(target: SshTarget, secrets: SshSecrets): Promise<{ ok: boolean; error?: string; category?: string }> {\n try {\n const conn = new SshConnection(target, secrets)\n await conn.getClient()\n conn.close()\n return { ok: true }\n } catch (err) {\n const e = toConnectionError(err)\n return { ok: false, error: e.message, category: e.category }\n }\n }\n\n release(target: SshTarget): void {\n const k = this.key(target)\n const entry = this.pool.get(k)\n if (!entry) return\n entry.connection.close()\n this.pool.delete(k)\n }\n\n closeAll(): void {\n for (const entry of this.pool.values()) entry.connection.close()\n this.pool.clear()\n }\n}\n"],"mappings":";;AASA,IAAa,oBAAb,MAA+B;CAGA;CAF7B,uBAAwB,IAAI,IAA8B;CAE1D,YAAY,KAA+B;EAAd,KAAA,MAAA;CAAe;CAE5C,IAAY,QAA2B;EACrC,OAAO,GAAG,OAAO,KAAK,GAAG,OAAO,KAAK,GAAG,OAAO;CACjD;CAEA,QAAQ,QAAmB,SAAoC;EAC7D,MAAM,IAAI,KAAK,IAAI,MAAM;EACzB,MAAM,WAAW,KAAK,KAAK,IAAI,CAAC;EAChC,IAAI,UAAU,OAAO,SAAS;EAC9B,MAAM,aAAa,IAAI,cAAc,QAAQ,OAAO;EACpD,KAAK,KAAK,IAAI,GAAG;GAAE;GAAQ;GAAS;EAAW,CAAC;EAChD,OAAO;CACT;CAEA,MAAM,KAAK,QAAmB,SAAkF;EAC9G,IAAI;GACF,MAAM,OAAO,IAAI,cAAc,QAAQ,OAAO;GAC9C,MAAM,KAAK,UAAU;GACrB,KAAK,MAAM;GACX,OAAO,EAAE,IAAI,KAAK;EACpB,SAAS,KAAK;GACZ,MAAM,IAAI,kBAAkB,GAAG;GAC/B,OAAO;IAAE,IAAI;IAAO,OAAO,EAAE;IAAS,UAAU,EAAE;GAAS;EAC7D;CACF;CAEA,QAAQ,QAAyB;EAC/B,MAAM,IAAI,KAAK,IAAI,MAAM;EACzB,MAAM,QAAQ,KAAK,KAAK,IAAI,CAAC;EAC7B,IAAI,CAAC,OAAO;EACZ,MAAM,WAAW,MAAM;EACvB,KAAK,KAAK,OAAO,CAAC;CACpB;CAEA,WAAiB;EACf,KAAK,MAAM,SAAS,KAAK,KAAK,OAAO,GAAG,MAAM,WAAW,MAAM;EAC/D,KAAK,KAAK,MAAM;CAClB;AACF"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { a as SshJumpHop, c as classifySshError, i as SshErrorCategory, l as toConnectionError, n as SshConnection, o as SshSecrets, r as SshConnectionError, s as SshTarget, t as SshAuthConfig } from "../connection-sIX25pkS.js";
|
|
2
|
+
export { SshAuthConfig, SshConnection, SshConnectionError, SshErrorCategory, SshJumpHop, SshSecrets, SshTarget, classifySshError, toConnectionError };
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { Client } from "ssh2";
|
|
2
|
+
//#region src/exec-ssh/connection.ts
|
|
3
|
+
const HANDSHAKE_TIMEOUT_MS = 3e4;
|
|
4
|
+
const MAX_RETRIES = 5;
|
|
5
|
+
const BASE_DELAY_MS = 500;
|
|
6
|
+
function classifySshError(err) {
|
|
7
|
+
const msg = err instanceof Error ? err.message.toLowerCase() : String(err).toLowerCase();
|
|
8
|
+
if (msg.includes("all configured authentication methods failed") || msg.includes("auth") || msg.includes("password") || msg.includes("publickey")) return "auth";
|
|
9
|
+
if (msg.includes("host key") || msg.includes("man-in-the-middle") || msg.includes("fingerprint")) return "hostkey";
|
|
10
|
+
if (msg.includes("timed out") || msg.includes("timeout") || msg.includes("ready timeout")) return "timeout";
|
|
11
|
+
if (msg.includes("econnrefused") || msg.includes("econnreset") || msg.includes("enotfound") || msg.includes("connect") || msg.includes("network")) return "network";
|
|
12
|
+
return "unknown";
|
|
13
|
+
}
|
|
14
|
+
function toConnectionError(err) {
|
|
15
|
+
const category = classifySshError(err);
|
|
16
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
17
|
+
const e = new Error(message);
|
|
18
|
+
e.category = category;
|
|
19
|
+
e.cause = err;
|
|
20
|
+
return e;
|
|
21
|
+
}
|
|
22
|
+
function buildConnectConfig(target, secrets) {
|
|
23
|
+
const cfg = {
|
|
24
|
+
host: target.host,
|
|
25
|
+
port: target.port,
|
|
26
|
+
username: target.username,
|
|
27
|
+
readyTimeout: HANDSHAKE_TIMEOUT_MS,
|
|
28
|
+
keepaliveInterval: 0,
|
|
29
|
+
keepaliveCountMax: 3
|
|
30
|
+
};
|
|
31
|
+
switch (target.auth.kind) {
|
|
32
|
+
case "password":
|
|
33
|
+
if (secrets.password) cfg.password = secrets.password;
|
|
34
|
+
break;
|
|
35
|
+
case "key":
|
|
36
|
+
if (secrets.privateKey) {
|
|
37
|
+
cfg.privateKey = secrets.privateKey;
|
|
38
|
+
if (secrets.passphrase) cfg.passphrase = secrets.passphrase;
|
|
39
|
+
}
|
|
40
|
+
break;
|
|
41
|
+
case "agent": cfg.agent = process.env.SSH_AUTH_SOCK ?? void 0;
|
|
42
|
+
}
|
|
43
|
+
return cfg;
|
|
44
|
+
}
|
|
45
|
+
var SshConnection = class {
|
|
46
|
+
target;
|
|
47
|
+
secrets;
|
|
48
|
+
client = null;
|
|
49
|
+
connecting = null;
|
|
50
|
+
closed = false;
|
|
51
|
+
constructor(target, secrets) {
|
|
52
|
+
this.target = target;
|
|
53
|
+
this.secrets = secrets;
|
|
54
|
+
}
|
|
55
|
+
async getClient() {
|
|
56
|
+
if (this.closed) throw toConnectionError(/* @__PURE__ */ new Error("connection closed"));
|
|
57
|
+
if (this.client) return this.client;
|
|
58
|
+
if (this.connecting) return this.connecting;
|
|
59
|
+
this.connecting = this.connectWithRetry();
|
|
60
|
+
try {
|
|
61
|
+
this.client = await this.connecting;
|
|
62
|
+
this.client.on("close", () => {
|
|
63
|
+
this.client = null;
|
|
64
|
+
});
|
|
65
|
+
this.client.on("error", () => {
|
|
66
|
+
this.client = null;
|
|
67
|
+
});
|
|
68
|
+
return this.client;
|
|
69
|
+
} finally {
|
|
70
|
+
this.connecting = null;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
async connectWithRetry() {
|
|
74
|
+
let lastErr;
|
|
75
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
76
|
+
if (this.closed) throw toConnectionError(/* @__PURE__ */ new Error("connection closed"));
|
|
77
|
+
try {
|
|
78
|
+
return await this.connectOnce();
|
|
79
|
+
} catch (err) {
|
|
80
|
+
lastErr = err;
|
|
81
|
+
const cat = classifySshError(err);
|
|
82
|
+
if (cat === "auth" || cat === "hostkey") throw toConnectionError(err);
|
|
83
|
+
if (attempt < MAX_RETRIES) {
|
|
84
|
+
const delay = BASE_DELAY_MS * Math.pow(2, attempt);
|
|
85
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
throw toConnectionError(lastErr);
|
|
91
|
+
}
|
|
92
|
+
connectOnce() {
|
|
93
|
+
return new Promise((resolve, reject) => {
|
|
94
|
+
const client = new Client();
|
|
95
|
+
let settled = false;
|
|
96
|
+
const finish = (fn) => {
|
|
97
|
+
if (settled) return;
|
|
98
|
+
settled = true;
|
|
99
|
+
fn();
|
|
100
|
+
};
|
|
101
|
+
client.once("ready", () => finish(() => resolve(client)));
|
|
102
|
+
client.on("error", (err) => {
|
|
103
|
+
try {
|
|
104
|
+
client.end();
|
|
105
|
+
} catch {}
|
|
106
|
+
finish(() => reject(err));
|
|
107
|
+
});
|
|
108
|
+
client.connect(buildConnectConfig(this.target, this.secrets));
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
close() {
|
|
112
|
+
this.closed = true;
|
|
113
|
+
this.connecting = null;
|
|
114
|
+
if (this.client) {
|
|
115
|
+
try {
|
|
116
|
+
this.client.end();
|
|
117
|
+
} catch {}
|
|
118
|
+
this.client = null;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
//#endregion
|
|
123
|
+
export { SshConnection, classifySshError, toConnectionError };
|
|
124
|
+
|
|
125
|
+
//# sourceMappingURL=connection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connection.js","names":[],"sources":["../../src/exec-ssh/connection.ts"],"sourcesContent":["import { Client, type ConnectConfig } from 'ssh2'\n\nexport interface SshAuthConfig {\n kind: 'password' | 'key' | 'agent'\n keyPath?: string\n}\n\nexport interface SshJumpHop {\n host: string\n port?: number\n username?: string\n keyPath?: string\n}\n\nexport interface SshTarget {\n host: string\n port: number\n username: string\n auth: SshAuthConfig\n jump?: SshJumpHop[]\n}\n\nexport interface SshSecrets {\n password?: string\n privateKey?: string\n passphrase?: string\n}\n\nexport type SshErrorCategory = 'auth' | 'network' | 'hostkey' | 'timeout' | 'unknown'\n\nexport interface SshConnectionError extends Error {\n category: SshErrorCategory\n cause?: unknown\n}\n\nconst HANDSHAKE_TIMEOUT_MS = 30_000\nconst MAX_RETRIES = 5\nconst BASE_DELAY_MS = 500\n\nexport function classifySshError(err: unknown): SshErrorCategory {\n const msg = err instanceof Error ? err.message.toLowerCase() : String(err).toLowerCase()\n if (msg.includes('all configured authentication methods failed') || msg.includes('auth') || msg.includes('password') || msg.includes('publickey')) return 'auth'\n if (msg.includes('host key') || msg.includes('man-in-the-middle') || msg.includes('fingerprint')) return 'hostkey'\n if (msg.includes('timed out') || msg.includes('timeout') || msg.includes('ready timeout')) return 'timeout'\n if (msg.includes('econnrefused') || msg.includes('econnreset') || msg.includes('enotfound') || msg.includes('connect') || msg.includes('network')) return 'network'\n return 'unknown'\n}\n\nexport function toConnectionError(err: unknown): SshConnectionError {\n const category = classifySshError(err)\n const message = err instanceof Error ? err.message : String(err)\n const e = new Error(message) as SshConnectionError\n e.category = category\n e.cause = err\n return e\n}\n\nfunction buildConnectConfig(target: SshTarget, secrets: SshSecrets): ConnectConfig {\n const cfg: ConnectConfig = {\n host: target.host,\n port: target.port,\n username: target.username,\n readyTimeout: HANDSHAKE_TIMEOUT_MS,\n keepaliveInterval: 0,\n keepaliveCountMax: 3,\n }\n switch (target.auth.kind) {\n case 'password':\n if (secrets.password) cfg.password = secrets.password\n break\n case 'key':\n if (secrets.privateKey) {\n cfg.privateKey = secrets.privateKey\n if (secrets.passphrase) cfg.passphrase = secrets.passphrase\n }\n break\n case 'agent':\n cfg.agent = process.env.SSH_AUTH_SOCK ?? undefined\n break\n }\n return cfg\n}\n\nexport class SshConnection {\n private client: Client | null = null\n private connecting: Promise<Client> | null = null\n private closed = false\n\n constructor(\n private readonly target: SshTarget,\n private readonly secrets: SshSecrets,\n ) {}\n\n async getClient(): Promise<Client> {\n if (this.closed) throw toConnectionError(new Error('connection closed'))\n if (this.client) return this.client\n if (this.connecting) return this.connecting\n this.connecting = this.connectWithRetry()\n try {\n this.client = await this.connecting\n this.client.on('close', () => {\n this.client = null\n })\n this.client.on('error', () => {\n this.client = null\n })\n return this.client\n } finally {\n this.connecting = null\n }\n }\n\n private async connectWithRetry(): Promise<Client> {\n let lastErr: unknown\n for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {\n if (this.closed) throw toConnectionError(new Error('connection closed'))\n try {\n return await this.connectOnce()\n } catch (err) {\n lastErr = err\n const cat = classifySshError(err)\n if (cat === 'auth' || cat === 'hostkey') throw toConnectionError(err)\n if (attempt < MAX_RETRIES) {\n const delay = BASE_DELAY_MS * Math.pow(2, attempt)\n await new Promise((r) => setTimeout(r, delay))\n continue\n }\n }\n }\n throw toConnectionError(lastErr)\n }\n\n private connectOnce(): Promise<Client> {\n return new Promise((resolve, reject) => {\n const client = new Client()\n let settled = false\n const finish = (fn: () => void) => {\n if (settled) return\n settled = true\n fn()\n }\n client.once('ready', () => finish(() => resolve(client)))\n client.on('error', (err: unknown) => {\n try {\n client.end()\n } catch {\n // client already torn down\n }\n finish(() => reject(err))\n })\n client.connect(buildConnectConfig(this.target, this.secrets))\n })\n }\n\n close(): void {\n this.closed = true\n this.connecting = null\n if (this.client) {\n try {\n this.client.end()\n } catch {}\n this.client = null\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
//#region src/exec-ssh/env-scrub.d.ts
|
|
2
|
+
declare function shouldScrubVar(name: string): boolean;
|
|
3
|
+
declare function scrubEnv(env: Record<string, string>): Record<string, string>;
|
|
4
|
+
declare const RUNNER_ENV_HINTS: string[];
|
|
5
|
+
//#endregion
|
|
6
|
+
export { RUNNER_ENV_HINTS, scrubEnv, shouldScrubVar };
|
|
7
|
+
//# sourceMappingURL=env-scrub.d.ts.map
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
//#region src/exec-ssh/env-scrub.ts
|
|
2
|
+
const DANGEROUS_PREFIXES = [
|
|
3
|
+
"DSH",
|
|
4
|
+
"MCAI",
|
|
5
|
+
"OPENAI",
|
|
6
|
+
"ANTHROPIC",
|
|
7
|
+
"DEEPSEEK",
|
|
8
|
+
"LLM",
|
|
9
|
+
"API_KEY",
|
|
10
|
+
"TOKEN",
|
|
11
|
+
"SECRET",
|
|
12
|
+
"PASSWORD",
|
|
13
|
+
"CREDENTIAL"
|
|
14
|
+
];
|
|
15
|
+
function shouldScrubVar(name) {
|
|
16
|
+
const upper = name.toUpperCase();
|
|
17
|
+
return DANGEROUS_PREFIXES.some((p) => upper === p || upper.startsWith(`${p}_`));
|
|
18
|
+
}
|
|
19
|
+
function scrubEnv(env) {
|
|
20
|
+
const result = {};
|
|
21
|
+
for (const [key, value] of Object.entries(env)) if (!shouldScrubVar(key)) result[key] = value;
|
|
22
|
+
return result;
|
|
23
|
+
}
|
|
24
|
+
const RUNNER_ENV_HINTS = [
|
|
25
|
+
"DSH_*",
|
|
26
|
+
"MCAI_*",
|
|
27
|
+
"OPENAI*",
|
|
28
|
+
"ANTHROPIC*",
|
|
29
|
+
"DEEPSEEK*",
|
|
30
|
+
"LLM_*",
|
|
31
|
+
"API_KEY*",
|
|
32
|
+
"TOKEN*",
|
|
33
|
+
"SECRET*",
|
|
34
|
+
"PASSWORD*",
|
|
35
|
+
"CREDENTIAL*"
|
|
36
|
+
];
|
|
37
|
+
//#endregion
|
|
38
|
+
export { RUNNER_ENV_HINTS, scrubEnv, shouldScrubVar };
|
|
39
|
+
|
|
40
|
+
//# sourceMappingURL=env-scrub.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env-scrub.js","names":[],"sources":["../../src/exec-ssh/env-scrub.ts"],"sourcesContent":["const DANGEROUS_PREFIXES = ['DSH', 'MCAI', 'OPENAI', 'ANTHROPIC', 'DEEPSEEK', 'LLM', 'API_KEY', 'TOKEN', 'SECRET', 'PASSWORD', 'CREDENTIAL']\n\nexport function shouldScrubVar(name: string): boolean {\n const upper = name.toUpperCase()\n return DANGEROUS_PREFIXES.some((p) => upper === p || upper.startsWith(`${p}_`))\n}\n\nexport function scrubEnv(env: Record<string, string>): Record<string, string> {\n const result: Record<string, string> = {}\n for (const [key, value] of Object.entries(env)) {\n if (!shouldScrubVar(key)) result[key] = value\n }\n return result\n}\n\nexport const RUNNER_ENV_HINTS = [\n 'DSH_*',\n 'MCAI_*',\n 'OPENAI*',\n 'ANTHROPIC*',\n 'DEEPSEEK*',\n 'LLM_*',\n 'API_KEY*',\n 'TOKEN*',\n 'SECRET*',\n 'PASSWORD*',\n 'CREDENTIAL*',\n]\n"],"mappings":";AAAA,MAAM,qBAAqB;CAAC;CAAO;CAAQ;CAAU;CAAa;CAAY;CAAO;CAAW;CAAS;CAAU;CAAY;AAAY;AAE3I,SAAgB,eAAe,MAAuB;CACpD,MAAM,QAAQ,KAAK,YAAY;CAC/B,OAAO,mBAAmB,MAAM,MAAM,UAAU,KAAK,MAAM,WAAW,GAAG,EAAE,EAAE,CAAC;AAChF;AAEA,SAAgB,SAAS,KAAqD;CAC5E,MAAM,SAAiC,CAAC;CACxC,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,GAAG,GAC3C,IAAI,CAAC,eAAe,GAAG,GAAG,OAAO,OAAO;CAE1C,OAAO;AACT;AAEA,MAAa,mBAAmB;CAC9B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { n as SshConnection } from "../connection-sIX25pkS.js";
|
|
2
|
+
//#region src/exec-ssh/executor.d.ts
|
|
3
|
+
interface ExecResult {
|
|
4
|
+
code: number;
|
|
5
|
+
signal?: string;
|
|
6
|
+
stdout: string;
|
|
7
|
+
stderr: string;
|
|
8
|
+
}
|
|
9
|
+
declare class SshExecutor {
|
|
10
|
+
private readonly connection;
|
|
11
|
+
private readonly nodeId;
|
|
12
|
+
private readonly audit?;
|
|
13
|
+
private sftpCache;
|
|
14
|
+
constructor(connection: SshConnection, nodeId?: string, audit?: ((op: string, target: string, result: string) => void) | undefined);
|
|
15
|
+
exec(argv: string[], cwd: string, env?: Record<string, string>, stdinData?: Buffer): Promise<ExecResult>;
|
|
16
|
+
sftp(): Promise<import('ssh2').SFTPWrapper>;
|
|
17
|
+
readFile(path: string): Promise<Buffer>;
|
|
18
|
+
writeFile(path: string, content: Buffer | string): Promise<void>;
|
|
19
|
+
listDir(path: string): Promise<Array<{
|
|
20
|
+
name: string;
|
|
21
|
+
isDir: boolean;
|
|
22
|
+
isFile: boolean;
|
|
23
|
+
isSymlink: boolean;
|
|
24
|
+
size: number;
|
|
25
|
+
mtime: number;
|
|
26
|
+
}>>;
|
|
27
|
+
stat(path: string): Promise<{
|
|
28
|
+
size: number;
|
|
29
|
+
mtime: number;
|
|
30
|
+
isDirectory: boolean;
|
|
31
|
+
isFile: boolean;
|
|
32
|
+
} | undefined>;
|
|
33
|
+
mkdir(path: string): Promise<void>;
|
|
34
|
+
remove(path: string): Promise<void>;
|
|
35
|
+
rename(src: string, dest: string): Promise<void>;
|
|
36
|
+
ensureRg(): Promise<void>;
|
|
37
|
+
pty(argv: string[], cwd: string, _env: Record<string, string>, cols: number, rows: number): Promise<{
|
|
38
|
+
channel: import('ssh2').ClientChannel;
|
|
39
|
+
onData: (cb: (chunk: Buffer) => void) => void;
|
|
40
|
+
resize: (cols: number, rows: number) => void;
|
|
41
|
+
kill: () => void;
|
|
42
|
+
}>;
|
|
43
|
+
}
|
|
44
|
+
//#endregion
|
|
45
|
+
export { ExecResult, SshExecutor };
|
|
46
|
+
//# sourceMappingURL=executor.d.ts.map
|