@artenx/dshb 0.0.3 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (166) hide show
  1. package/cordis.patch.yml +8 -49
  2. package/lib/auth/credentials.d.ts +30 -0
  3. package/lib/auth/credentials.js +121 -0
  4. package/lib/auth/credentials.js.map +1 -0
  5. package/lib/auth/decorate-webserver.d.ts +7 -0
  6. package/lib/auth/decorate-webserver.js +75 -0
  7. package/lib/auth/decorate-webserver.js.map +1 -0
  8. package/lib/auth/gate.d.ts +14 -0
  9. package/lib/auth/gate.js +45 -0
  10. package/lib/auth/gate.js.map +1 -0
  11. package/lib/auth/index.d.ts +8 -0
  12. package/lib/auth/index.js +51 -0
  13. package/lib/auth/index.js.map +1 -0
  14. package/lib/auth/login-page.d.ts +5 -0
  15. package/lib/auth/login-page.js +87 -0
  16. package/lib/auth/login-page.js.map +1 -0
  17. package/lib/auth/loopback-compat.d.ts +6 -0
  18. package/lib/auth/loopback-compat.js +94 -0
  19. package/lib/auth/loopback-compat.js.map +1 -0
  20. package/lib/auth/loopback.d.ts +7 -0
  21. package/lib/auth/loopback.js +24 -0
  22. package/lib/auth/loopback.js.map +1 -0
  23. package/lib/auth/ratelimit.d.ts +11 -0
  24. package/lib/auth/ratelimit.js +37 -0
  25. package/lib/auth/ratelimit.js.map +1 -0
  26. package/lib/auth/routes.d.ts +8 -0
  27. package/lib/auth/routes.js +224 -0
  28. package/lib/auth/routes.js.map +1 -0
  29. package/lib/auth/session.d.ts +11 -0
  30. package/lib/auth/session.js +52 -0
  31. package/lib/auth/session.js.map +1 -0
  32. package/lib/auth/startup.d.ts +15 -0
  33. package/lib/auth/startup.js +68 -0
  34. package/lib/auth/startup.js.map +1 -0
  35. package/lib/auth/webserver.d.ts +18 -0
  36. package/lib/auth/webserver.js +60 -0
  37. package/lib/auth/webserver.js.map +1 -0
  38. package/lib/client.js +2770 -0
  39. package/lib/client.js.map +1 -0
  40. package/lib/connection-sIX25pkS.d.ts +46 -0
  41. package/lib/core/audit.d.ts +16 -0
  42. package/lib/core/audit.js +38 -0
  43. package/lib/core/audit.js.map +1 -0
  44. package/lib/core/index.d.ts +8 -0
  45. package/lib/core/index.js +77 -0
  46. package/lib/core/index.js.map +1 -0
  47. package/lib/core/known-hosts.d.ts +23 -0
  48. package/lib/core/known-hosts.js +76 -0
  49. package/lib/core/known-hosts.js.map +1 -0
  50. package/lib/core/node-registry.d.ts +108 -0
  51. package/lib/core/node-registry.js +191 -0
  52. package/lib/core/node-registry.js.map +1 -0
  53. package/lib/core/routes.d.ts +9 -0
  54. package/lib/core/routes.js +404 -0
  55. package/lib/core/routes.js.map +1 -0
  56. package/lib/core/ssh-config.d.ts +23 -0
  57. package/lib/core/ssh-config.js +70 -0
  58. package/lib/core/ssh-config.js.map +1 -0
  59. package/lib/core/test.d.ts +43 -0
  60. package/lib/core/test.js +116 -0
  61. package/lib/core/test.js.map +1 -0
  62. package/lib/core/workspace-bindings.d.ts +28 -0
  63. package/lib/core/workspace-bindings.js +86 -0
  64. package/lib/core/workspace-bindings.js.map +1 -0
  65. package/lib/core/workspaces.d.ts +9 -0
  66. package/lib/core/workspaces.js +283 -0
  67. package/lib/core/workspaces.js.map +1 -0
  68. package/lib/exec-docker/daemon-runner.d.ts +21 -0
  69. package/lib/exec-docker/daemon-runner.js +326 -0
  70. package/lib/exec-docker/daemon-runner.js.map +1 -0
  71. package/lib/exec-docker/docker-backend.d.ts +48 -0
  72. package/lib/exec-docker/docker-backend.js +1 -0
  73. package/lib/exec-docker/docker-client.d.ts +46 -0
  74. package/lib/exec-docker/docker-client.js +302 -0
  75. package/lib/exec-docker/docker-client.js.map +1 -0
  76. package/lib/exec-docker/docker-routes.d.ts +6 -0
  77. package/lib/exec-docker/docker-routes.js +215 -0
  78. package/lib/exec-docker/docker-routes.js.map +1 -0
  79. package/lib/exec-docker/fs-provider.d.ts +50 -0
  80. package/lib/exec-docker/fs-provider.js +92 -0
  81. package/lib/exec-docker/fs-provider.js.map +1 -0
  82. package/lib/exec-docker/host-runner.d.ts +29 -0
  83. package/lib/exec-docker/host-runner.js +17 -0
  84. package/lib/exec-docker/host-runner.js.map +1 -0
  85. package/lib/exec-docker/index.d.ts +21 -0
  86. package/lib/exec-docker/index.js +137 -0
  87. package/lib/exec-docker/index.js.map +1 -0
  88. package/lib/exec-docker/process-provider.d.ts +20 -0
  89. package/lib/exec-docker/process-provider.js +75 -0
  90. package/lib/exec-docker/process-provider.js.map +1 -0
  91. package/lib/exec-docker/provision.d.ts +24 -0
  92. package/lib/exec-docker/provision.js +103 -0
  93. package/lib/exec-docker/provision.js.map +1 -0
  94. package/lib/exec-docker/remote-docker-cli.d.ts +52 -0
  95. package/lib/exec-docker/remote-docker-cli.js +155 -0
  96. package/lib/exec-docker/remote-docker-cli.js.map +1 -0
  97. package/lib/exec-docker/world.d.ts +37 -0
  98. package/lib/exec-docker/world.js +62 -0
  99. package/lib/exec-docker/world.js.map +1 -0
  100. package/lib/exec-ssh/connection-pool.d.ts +25 -0
  101. package/lib/exec-ssh/connection-pool.js +54 -0
  102. package/lib/exec-ssh/connection-pool.js.map +1 -0
  103. package/lib/exec-ssh/connection.d.ts +2 -0
  104. package/lib/exec-ssh/connection.js +125 -0
  105. package/lib/exec-ssh/connection.js.map +1 -0
  106. package/lib/exec-ssh/env-scrub.d.ts +7 -0
  107. package/lib/exec-ssh/env-scrub.js +40 -0
  108. package/lib/exec-ssh/env-scrub.js.map +1 -0
  109. package/lib/exec-ssh/executor.d.ts +46 -0
  110. package/lib/exec-ssh/executor.js +212 -0
  111. package/lib/exec-ssh/executor.js.map +1 -0
  112. package/lib/exec-ssh/fs-provider.d.ts +50 -0
  113. package/lib/exec-ssh/fs-provider.js +92 -0
  114. package/lib/exec-ssh/fs-provider.js.map +1 -0
  115. package/lib/exec-ssh/index.d.ts +16 -0
  116. package/lib/exec-ssh/index.js +30 -0
  117. package/lib/exec-ssh/index.js.map +1 -0
  118. package/lib/exec-ssh/process-provider.d.ts +20 -0
  119. package/lib/exec-ssh/process-provider.js +75 -0
  120. package/lib/exec-ssh/process-provider.js.map +1 -0
  121. package/lib/exec-ssh/runner.d.ts +13 -0
  122. package/lib/exec-ssh/runner.js +62 -0
  123. package/lib/exec-ssh/runner.js.map +1 -0
  124. package/lib/exec-ssh/world.d.ts +79 -0
  125. package/lib/exec-ssh/world.js +95 -0
  126. package/lib/exec-ssh/world.js.map +1 -0
  127. package/lib/index.d.ts +2 -0
  128. package/lib/index.js +2 -0
  129. package/lib/router/decorate-fs.d.ts +6 -0
  130. package/lib/router/decorate-fs.js +78 -0
  131. package/lib/router/decorate-fs.js.map +1 -0
  132. package/lib/router/decorate-shell.d.ts +6 -0
  133. package/lib/router/decorate-shell.js +35 -0
  134. package/lib/router/decorate-shell.js.map +1 -0
  135. package/lib/router/decorate-subprocess.d.ts +6 -0
  136. package/lib/router/decorate-subprocess.js +35 -0
  137. package/lib/router/decorate-subprocess.js.map +1 -0
  138. package/lib/router/index.d.ts +10 -0
  139. package/lib/router/index.js +21 -0
  140. package/lib/router/index.js.map +1 -0
  141. package/lib/router/resolve.d.ts +22 -0
  142. package/lib/router/resolve.js +45 -0
  143. package/lib/router/resolve.js.map +1 -0
  144. package/lib/router/router-fs.d.ts +25 -0
  145. package/lib/router/router-fs.js +64 -0
  146. package/lib/router/router-fs.js.map +1 -0
  147. package/lib/router/router-shell.d.ts +14 -0
  148. package/lib/router/router-shell.js +36 -0
  149. package/lib/router/router-shell.js.map +1 -0
  150. package/lib/router/router-subprocess.d.ts +13 -0
  151. package/lib/router/router-subprocess.js +33 -0
  152. package/lib/router/router-subprocess.js.map +1 -0
  153. package/lib/router/types.d.ts +2 -0
  154. package/lib/router/types.js +1 -0
  155. package/lib/types-C0TtEO7z.d.ts +67 -0
  156. package/lib/ui/decorate-picker.d.ts +6 -0
  157. package/lib/ui/decorate-picker.js +58 -0
  158. package/lib/ui/decorate-picker.js.map +1 -0
  159. package/lib/ui/index.d.ts +8 -0
  160. package/lib/ui/index.js +11 -0
  161. package/lib/ui/index.js.map +1 -0
  162. package/lib/ui/picker.d.ts +10 -0
  163. package/lib/ui/picker.js +58 -0
  164. package/lib/ui/picker.js.map +1 -0
  165. package/package.json +158 -9
  166. package/LICENSE +0 -21
@@ -0,0 +1,302 @@
1
+ import { parseDaemonHost } from "./daemon-runner.js";
2
+ import { request } from "node:http";
3
+ import { request as request$1 } from "node:https";
4
+ //#region src/exec-docker/docker-client.ts
5
+ const DAEMON = parseDaemonHost();
6
+ function requestRaw(opts, onResponse) {
7
+ const common = { ...opts };
8
+ if (DAEMON.socketPath) return request({
9
+ ...common,
10
+ socketPath: DAEMON.socketPath
11
+ }, onResponse);
12
+ return (DAEMON.tls ? request$1 : request)({
13
+ ...common,
14
+ hostname: DAEMON.host,
15
+ port: DAEMON.port
16
+ }, onResponse);
17
+ }
18
+ function dockerRequest(method, path, body) {
19
+ const headers = body ? { "content-type": "application/json" } : {};
20
+ if (DAEMON.socketPath) return new Promise((resolve, reject) => {
21
+ const req = request({
22
+ method,
23
+ path,
24
+ socketPath: DAEMON.socketPath,
25
+ headers
26
+ }, (res) => {
27
+ const chunks = [];
28
+ res.on("data", (c) => chunks.push(c));
29
+ res.on("end", () => {
30
+ resolve({
31
+ statusCode: res.statusCode ?? 0,
32
+ headers: res.headers,
33
+ body: Buffer.concat(chunks)
34
+ });
35
+ });
36
+ res.on("error", reject);
37
+ });
38
+ req.on("error", reject);
39
+ if (body) req.write(JSON.stringify(body));
40
+ req.end();
41
+ });
42
+ const mod = DAEMON.tls ? request$1 : request;
43
+ return new Promise((resolve, reject) => {
44
+ const req = mod({
45
+ method,
46
+ path,
47
+ hostname: DAEMON.host,
48
+ port: DAEMON.port,
49
+ headers
50
+ }, (res) => {
51
+ const chunks = [];
52
+ res.on("data", (c) => chunks.push(c));
53
+ res.on("end", () => {
54
+ resolve({
55
+ statusCode: res.statusCode ?? 0,
56
+ headers: res.headers,
57
+ body: Buffer.concat(chunks)
58
+ });
59
+ });
60
+ res.on("error", reject);
61
+ });
62
+ req.on("error", reject);
63
+ if (body) req.write(JSON.stringify(body));
64
+ req.end();
65
+ });
66
+ }
67
+ function demux(data) {
68
+ const stdout = [];
69
+ const stderr = [];
70
+ let pos = 0;
71
+ while (pos + 8 <= data.length) {
72
+ const streamType = data[pos];
73
+ const length = data.readUInt32BE(pos + 4);
74
+ pos += 8;
75
+ if (pos + length > data.length) break;
76
+ const payload = data.subarray(pos, pos + length);
77
+ pos += length;
78
+ if (streamType === 1) stdout.push(payload);
79
+ else if (streamType === 2) stderr.push(payload);
80
+ }
81
+ return {
82
+ stdout,
83
+ stderr
84
+ };
85
+ }
86
+ function shellQuote(s) {
87
+ return `'${s.replace(/'/g, "'\\''")}'`;
88
+ }
89
+ var DockerClient = class {
90
+ containerId;
91
+ constructor(containerId) {
92
+ this.containerId = containerId;
93
+ }
94
+ async ping() {
95
+ try {
96
+ return (await dockerRequest("GET", "/_ping")).statusCode === 200;
97
+ } catch {
98
+ return false;
99
+ }
100
+ }
101
+ async inspect() {
102
+ const res = await dockerRequest("GET", `/containers/${this.containerId}/json`);
103
+ if (res.statusCode !== 200) return void 0;
104
+ const data = JSON.parse(res.body.toString("utf8"));
105
+ return {
106
+ running: data.State?.Running ?? false,
107
+ name: data.Name ?? this.containerId
108
+ };
109
+ }
110
+ async exec(argv, cwd, env = {}, stdinData) {
111
+ const createRes = await dockerRequest("POST", `/containers/${this.containerId}/exec`, {
112
+ AttachStdout: true,
113
+ AttachStderr: true,
114
+ AttachStdin: stdinData !== void 0,
115
+ Tty: false,
116
+ WorkingDir: cwd,
117
+ Env: Object.entries(env).map(([k, v]) => `${k}=${v}`),
118
+ Cmd: argv
119
+ });
120
+ if (createRes.statusCode !== 201) return {
121
+ code: 126,
122
+ stdout: "",
123
+ stderr: `docker exec create failed: ${createRes.body.toString("utf8")}`
124
+ };
125
+ const execId = JSON.parse(createRes.body.toString("utf8")).Id;
126
+ if (!execId) return {
127
+ code: 126,
128
+ stdout: "",
129
+ stderr: "docker exec: no Id returned"
130
+ };
131
+ return new Promise((resolve, reject) => {
132
+ const req = requestRaw({
133
+ method: "POST",
134
+ path: `/exec/${execId}/start`,
135
+ headers: {
136
+ "content-type": "application/json",
137
+ connection: "upgrade"
138
+ }
139
+ }, (res) => {
140
+ const chunks = [];
141
+ res.on("data", (c) => chunks.push(c));
142
+ res.on("end", () => {
143
+ const demuxed = demux(Buffer.concat(chunks));
144
+ const inspectReq = requestRaw({
145
+ method: "GET",
146
+ path: `/exec/${execId}/json`,
147
+ headers: {}
148
+ }, (inspectRes) => {
149
+ const ichunks = [];
150
+ inspectRes.on("data", (c) => ichunks.push(c));
151
+ inspectRes.on("end", () => {
152
+ let code = 0;
153
+ try {
154
+ code = JSON.parse(Buffer.concat(ichunks).toString("utf8")).ExitCode ?? 0;
155
+ } catch {}
156
+ resolve({
157
+ code,
158
+ stdout: Buffer.concat(demuxed.stdout).toString("utf8"),
159
+ stderr: Buffer.concat(demuxed.stderr).toString("utf8")
160
+ });
161
+ });
162
+ inspectRes.on("error", () => resolve({
163
+ code: 0,
164
+ stdout: Buffer.concat(demuxed.stdout).toString("utf8"),
165
+ stderr: Buffer.concat(demuxed.stderr).toString("utf8")
166
+ }));
167
+ });
168
+ inspectReq.on("error", () => resolve({
169
+ code: 0,
170
+ stdout: Buffer.concat(demuxed.stdout).toString("utf8"),
171
+ stderr: Buffer.concat(demuxed.stderr).toString("utf8")
172
+ }));
173
+ inspectReq.end();
174
+ });
175
+ res.on("error", reject);
176
+ });
177
+ req.on("error", reject);
178
+ req.write(JSON.stringify({
179
+ Detach: false,
180
+ Tty: false
181
+ }));
182
+ if (stdinData) req.write(stdinData);
183
+ req.end();
184
+ });
185
+ }
186
+ async execShell(command, cwd) {
187
+ return this.exec([
188
+ "bash",
189
+ "-c",
190
+ command
191
+ ], cwd);
192
+ }
193
+ async readFile(path) {
194
+ const result = await this.exec(["cat", path], "/");
195
+ return Buffer.from(result.stdout, "utf8");
196
+ }
197
+ async writeFile(path, content) {
198
+ const b64 = (Buffer.isBuffer(content) ? content : Buffer.from(content, "utf8")).toString("base64");
199
+ await this.execShell(`printf '%s' '${b64}' | base64 -d > ${shellQuote(path)}`, "/");
200
+ }
201
+ async listDir(path) {
202
+ return (await this.execShell(`ls -1pa --time-style=+%s ${shellQuote(path)} 2>/dev/null`, "/")).stdout.split("\n").filter((line) => line.trim()).map((line) => {
203
+ const name = line.replace(/[/@*]$/, "").trim();
204
+ if (!name) return null;
205
+ const isDir = line.endsWith("/");
206
+ const isSymlink = line.endsWith("@");
207
+ return {
208
+ name,
209
+ isDir,
210
+ isFile: !isDir && !isSymlink,
211
+ isSymlink,
212
+ size: 0,
213
+ mtime: 0
214
+ };
215
+ }).filter((e) => e !== null);
216
+ }
217
+ async stat(path) {
218
+ const line = (await this.execShell(`stat -c '%s %Y %F' ${shellQuote(path)} 2>/dev/null`, "/")).stdout.trim();
219
+ if (!line) return void 0;
220
+ const parts = line.split(/\s+/);
221
+ if (parts.length < 3) return void 0;
222
+ const size = Number(parts[0]);
223
+ const mtime = Number(parts[1]) * 1e3;
224
+ const ftype = parts.slice(2).join(" ");
225
+ return {
226
+ size,
227
+ mtime,
228
+ isDirectory: ftype.includes("directory"),
229
+ isFile: ftype.includes("regular file") || ftype.includes("regular empty file")
230
+ };
231
+ }
232
+ async mkdir(path) {
233
+ await this.exec([
234
+ "mkdir",
235
+ "-p",
236
+ path
237
+ ], "/");
238
+ }
239
+ async remove(path) {
240
+ await this.exec([
241
+ "rm",
242
+ "-f",
243
+ path
244
+ ], "/");
245
+ }
246
+ async rename(src, dest) {
247
+ await this.exec([
248
+ "mv",
249
+ src,
250
+ dest
251
+ ], "/");
252
+ }
253
+ async pty(argv, cwd, cols, rows) {
254
+ const createRes = await dockerRequest("POST", `/containers/${this.containerId}/exec`, {
255
+ AttachStdout: true,
256
+ AttachStderr: true,
257
+ AttachStdin: true,
258
+ Tty: true,
259
+ WorkingDir: cwd,
260
+ Cmd: argv
261
+ });
262
+ if (createRes.statusCode !== 201) throw new Error("docker exec create failed");
263
+ const execId = JSON.parse(createRes.body.toString("utf8")).Id;
264
+ return new Promise((resolve, reject) => {
265
+ const req = requestRaw({
266
+ method: "POST",
267
+ path: `/exec/${execId}/start`,
268
+ headers: {
269
+ "content-type": "application/json",
270
+ connection: "upgrade"
271
+ }
272
+ }, (res) => {
273
+ resolve({
274
+ stream: res,
275
+ resize: (c, r) => {
276
+ requestRaw({
277
+ method: "POST",
278
+ path: `/exec/${execId}/resize?h=${r}&w=${c}`,
279
+ headers: {}
280
+ }, () => {}).end();
281
+ },
282
+ kill: () => {
283
+ res.destroy();
284
+ }
285
+ });
286
+ });
287
+ req.on("error", reject);
288
+ req.write(JSON.stringify({
289
+ Detach: false,
290
+ Tty: true
291
+ }));
292
+ req.end();
293
+ });
294
+ }
295
+ async ensureRg() {
296
+ if ((await this.execShell("command -v rg || echo __missing__", "/")).stdout.includes("__missing__")) await this.execShell("apk add --no-cache ripgrep 2>/dev/null || apt-get install -y ripgrep 2>/dev/null || true", "/");
297
+ }
298
+ };
299
+ //#endregion
300
+ export { DockerClient };
301
+
302
+ //# sourceMappingURL=docker-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"docker-client.js","names":["httpRequest","httpsRequest"],"sources":["../../src/exec-docker/docker-client.ts"],"sourcesContent":["import type { IncomingMessage, ClientRequest } from 'node:http'\nimport { request as httpRequest } from 'node:http'\nimport { request as httpsRequest } from 'node:https'\nimport type { DockerBackend } from './docker-backend.js'\nimport { parseDaemonHost } from './daemon-runner.js'\n\nconst DAEMON = parseDaemonHost()\n\ninterface ExecResult {\n code: number\n stdout: string\n stderr: string\n}\n\ninterface Demuxed {\n stdout: Buffer[]\n stderr: Buffer[]\n}\n\nfunction requestRaw(\n opts: { method: string; path: string; headers: Record<string, string> },\n onResponse: (res: IncomingMessage) => void,\n): ClientRequest {\n const common = { ...opts }\n if (DAEMON.socketPath) return httpRequest({ ...common, socketPath: DAEMON.socketPath } as never, onResponse)\n const mod = DAEMON.tls ? httpsRequest : httpRequest\n return mod({ ...common, hostname: DAEMON.host, port: DAEMON.port } as never, onResponse)\n}\n\nfunction dockerRequest(method: string, path: string, body?: unknown): Promise<{ statusCode: number; headers: Record<string, string>; body: Buffer }> {\n const headers = body ? { 'content-type': 'application/json' } : {}\n if (DAEMON.socketPath) {\n return new Promise((resolve, reject) => {\n const req = httpRequest({ method, path, socketPath: DAEMON.socketPath as string, headers }, (res) => {\n const chunks: Buffer[] = []\n res.on('data', (c: Buffer) => chunks.push(c))\n res.on('end', () => {\n resolve({\n statusCode: res.statusCode ?? 0,\n headers: res.headers as Record<string, string>,\n body: Buffer.concat(chunks),\n })\n })\n res.on('error', reject)\n })\n req.on('error', reject)\n if (body) req.write(JSON.stringify(body))\n req.end()\n })\n }\n const mod = DAEMON.tls ? httpsRequest : httpRequest\n return new Promise((resolve, reject) => {\n const req = mod({ method, path, hostname: DAEMON.host, port: DAEMON.port, headers }, (res) => {\n const chunks: Buffer[] = []\n res.on('data', (c: Buffer) => chunks.push(c))\n res.on('end', () => {\n resolve({\n statusCode: res.statusCode ?? 0,\n headers: res.headers as Record<string, string>,\n body: Buffer.concat(chunks),\n })\n })\n res.on('error', reject)\n })\n req.on('error', reject)\n if (body) req.write(JSON.stringify(body))\n req.end()\n })\n}\n\nfunction demux(data: Buffer): Demuxed {\n const stdout: Buffer[] = []\n const stderr: Buffer[] = []\n let pos = 0\n while (pos + 8 <= data.length) {\n const streamType = data[pos]\n const length = data.readUInt32BE(pos + 4)\n pos += 8\n if (pos + length > data.length) break\n const payload = data.subarray(pos, pos + length)\n pos += length\n if (streamType === 1) stdout.push(payload)\n else if (streamType === 2) stderr.push(payload)\n }\n return { stdout, stderr }\n}\n\nfunction shellQuote(s: string): string {\n return `'${s.replace(/'/g, \"'\\\\''\")}'`\n}\n\nexport class DockerClient implements DockerBackend {\n private readonly containerId: string\n\n constructor(containerId: string) {\n this.containerId = containerId\n }\n\n async ping(): Promise<boolean> {\n try {\n const res = await dockerRequest('GET', '/_ping')\n return res.statusCode === 200\n } catch {\n return false\n }\n }\n\n async inspect(): Promise<{ running: boolean; name: string } | undefined> {\n const res = await dockerRequest('GET', `/containers/${this.containerId}/json`)\n if (res.statusCode !== 200) return undefined\n const data = JSON.parse(res.body.toString('utf8'))\n return { running: data.State?.Running ?? false, name: data.Name ?? this.containerId }\n }\n\n async exec(argv: string[], cwd: string, env: Record<string, string> = {}, stdinData?: Buffer): Promise<ExecResult> {\n const createRes = await dockerRequest('POST', `/containers/${this.containerId}/exec`, {\n AttachStdout: true,\n AttachStderr: true,\n AttachStdin: stdinData !== undefined,\n Tty: false,\n WorkingDir: cwd,\n Env: Object.entries(env).map(([k, v]) => `${k}=${v}`),\n Cmd: argv,\n })\n if (createRes.statusCode !== 201) {\n const msg = createRes.body.toString('utf8')\n return { code: 126, stdout: '', stderr: `docker exec create failed: ${msg}` }\n }\n const execId = JSON.parse(createRes.body.toString('utf8')).Id\n if (!execId) return { code: 126, stdout: '', stderr: 'docker exec: no Id returned' }\n\n return new Promise<ExecResult>((resolve, reject) => {\n const req = requestRaw(\n {\n method: 'POST',\n path: `/exec/${execId}/start`,\n headers: { 'content-type': 'application/json', connection: 'upgrade' },\n },\n (res) => {\n const chunks: Buffer[] = []\n res.on('data', (c: Buffer) => chunks.push(c))\n res.on('end', () => {\n const raw = Buffer.concat(chunks)\n const demuxed = demux(raw)\n const inspectReq = requestRaw(\n { method: 'GET', path: `/exec/${execId}/json`, headers: {} },\n (inspectRes) => {\n const ichunks: Buffer[] = []\n inspectRes.on('data', (c: Buffer) => ichunks.push(c))\n inspectRes.on('end', () => {\n let code = 0\n try {\n const info = JSON.parse(Buffer.concat(ichunks).toString('utf8'))\n code = info.ExitCode ?? 0\n } catch {}\n resolve({\n code,\n stdout: Buffer.concat(demuxed.stdout).toString('utf8'),\n stderr: Buffer.concat(demuxed.stderr).toString('utf8'),\n })\n })\n inspectRes.on('error', () => resolve({ code: 0, stdout: Buffer.concat(demuxed.stdout).toString('utf8'), stderr: Buffer.concat(demuxed.stderr).toString('utf8') }))\n },\n )\n inspectReq.on('error', () => resolve({ code: 0, stdout: Buffer.concat(demuxed.stdout).toString('utf8'), stderr: Buffer.concat(demuxed.stderr).toString('utf8') }))\n inspectReq.end()\n })\n res.on('error', reject)\n },\n )\n req.on('error', reject)\n req.write(JSON.stringify({ Detach: false, Tty: false }))\n if (stdinData) req.write(stdinData)\n req.end()\n })\n }\n\n async execShell(command: string, cwd: string): Promise<ExecResult> {\n return this.exec(['bash', '-c', command], cwd)\n }\n\n async readFile(path: string): Promise<Buffer> {\n const result = await this.exec(['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.execShell(`printf '%s' '${b64}' | base64 -d > ${shellQuote(path)}`, '/')\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.execShell(`ls -1pa --time-style=+%s ${shellQuote(path)} 2>/dev/null`, '/')\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 {\n name,\n isDir,\n isFile: !isDir && !isSymlink,\n isSymlink,\n size: 0,\n mtime: 0,\n }\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.execShell(`stat -c '%s %Y %F' ${shellQuote(path)} 2>/dev/null`, '/')\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 {\n size,\n mtime,\n isDirectory: ftype.includes('directory'),\n isFile: ftype.includes('regular file') || ftype.includes('regular empty file'),\n }\n }\n\n async mkdir(path: string): Promise<void> {\n await this.exec(['mkdir', '-p', path], '/')\n }\n\n async remove(path: string): Promise<void> {\n await this.exec(['rm', '-f', path], '/')\n }\n\n async rename(src: string, dest: string): Promise<void> {\n await this.exec(['mv', src, dest], '/')\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 const createRes = await dockerRequest('POST', `/containers/${this.containerId}/exec`, {\n AttachStdout: true,\n AttachStderr: true,\n AttachStdin: true,\n Tty: true,\n WorkingDir: cwd,\n Cmd: argv,\n })\n if (createRes.statusCode !== 201) throw new Error('docker exec create failed')\n const execId = JSON.parse(createRes.body.toString('utf8')).Id\n\n return new Promise((resolve, reject) => {\n const req = requestRaw(\n {\n method: 'POST',\n path: `/exec/${execId}/start`,\n headers: { 'content-type': 'application/json', connection: 'upgrade' },\n },\n (res) => {\n resolve({\n stream: res as unknown as NodeJS.ReadWriteStream,\n resize: (c: number, r: number) => {\n const r2 = requestRaw({ method: 'POST', path: `/exec/${execId}/resize?h=${r}&w=${c}`, headers: {} }, () => {})\n r2.end()\n },\n kill: () => {\n res.destroy()\n },\n })\n },\n )\n req.on('error', reject)\n req.write(JSON.stringify({ Detach: false, Tty: true }))\n req.end()\n })\n }\n\n async ensureRg(): Promise<void> {\n const result = await this.execShell('command -v rg || echo __missing__', '/')\n if (result.stdout.includes('__missing__')) {\n await this.execShell('apk add --no-cache ripgrep 2>/dev/null || apt-get install -y ripgrep 2>/dev/null || true', '/')\n }\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import { Context } from "@deepseek-ai/cordis";
2
+ //#region src/exec-docker/docker-routes.d.ts
3
+ declare function registerDockerRoutes(ctx: Context): void;
4
+ //#endregion
5
+ export { registerDockerRoutes };
6
+ //# sourceMappingURL=docker-routes.d.ts.map
@@ -0,0 +1,215 @@
1
+ import { listContainers, provisionContainer } from "./provision.js";
2
+ import { resolveHostRunner } from "./host-runner.js";
3
+ //#region src/exec-docker/docker-routes.ts
4
+ function sendJson(res, status, body) {
5
+ res.writeHead(status, { "content-type": "application/json" });
6
+ res.end(JSON.stringify(body));
7
+ }
8
+ function readBody(req) {
9
+ return new Promise((resolve) => {
10
+ if ((req.headers["content-type"] ?? "") !== "application/json") {
11
+ resolve(null);
12
+ return;
13
+ }
14
+ const chunks = [];
15
+ let size = 0;
16
+ req.on("data", (chunk) => {
17
+ size += chunk.length;
18
+ if (size > 65536) {
19
+ resolve(null);
20
+ req.destroy();
21
+ return;
22
+ }
23
+ chunks.push(chunk);
24
+ });
25
+ req.on("end", () => {
26
+ try {
27
+ resolve(JSON.parse(Buffer.concat(chunks).toString("utf8")));
28
+ } catch {
29
+ resolve(null);
30
+ }
31
+ });
32
+ req.on("error", () => resolve(null));
33
+ });
34
+ }
35
+ function pathSegments(req) {
36
+ return new URL(req.url ?? "/", "http://x").pathname.split("/").filter(Boolean);
37
+ }
38
+ function registerDockerRoutes(ctx) {
39
+ const webServer = ctx.webServer;
40
+ ctx.effect(() => webServer.register({
41
+ kind: "prefix",
42
+ path: "/api/dshb/docker",
43
+ handler: async (req, res) => {
44
+ const segs = pathSegments(req);
45
+ const nodeId = segs[3];
46
+ const action = segs[4];
47
+ const nodeRegistry = ctx.get("nodeRegistry");
48
+ const node = nodeId ? nodeRegistry?.get(nodeId) : void 0;
49
+ if (!node) {
50
+ sendJson(res, 404, {
51
+ ok: false,
52
+ error: "node not found"
53
+ });
54
+ return;
55
+ }
56
+ if (req.method === "POST" && action === "provision") {
57
+ const provisioner = ctx.get("dshbDockerProvisioner");
58
+ if (provisioner) {
59
+ try {
60
+ const result = await provisioner.provision(nodeId);
61
+ sendJson(res, 201, {
62
+ ok: true,
63
+ containerId: result.containerId,
64
+ name: result.name
65
+ });
66
+ } catch (err) {
67
+ sendJson(res, 502, {
68
+ ok: false,
69
+ error: err instanceof Error ? err.message : String(err)
70
+ });
71
+ }
72
+ return;
73
+ }
74
+ const body = await readBody(req);
75
+ const image = String(body?.image ?? "");
76
+ if (!image) {
77
+ sendJson(res, 400, {
78
+ ok: false,
79
+ error: "需要 image"
80
+ });
81
+ return;
82
+ }
83
+ const fallbackRunner = await resolveHostRunner(ctx, node);
84
+ if (!fallbackRunner) {
85
+ sendJson(res, 503, {
86
+ ok: false,
87
+ error: "节点执行世界未就绪"
88
+ });
89
+ return;
90
+ }
91
+ try {
92
+ const result = await provisionContainer(fallbackRunner, {
93
+ image,
94
+ cpus: body?.cpus ? Number(body.cpus) : void 0,
95
+ memoryMB: body?.memoryMB ? Number(body.memoryMB) : void 0,
96
+ name: body?.name ? String(body.name) : void 0
97
+ });
98
+ sendJson(res, 201, {
99
+ ok: true,
100
+ containerId: result.containerId,
101
+ name: result.name
102
+ });
103
+ } catch (err) {
104
+ sendJson(res, 502, {
105
+ ok: false,
106
+ error: err instanceof Error ? err.message : String(err)
107
+ });
108
+ }
109
+ return;
110
+ }
111
+ const runner = await resolveHostRunner(ctx, node);
112
+ if (!runner) {
113
+ sendJson(res, 503, {
114
+ ok: false,
115
+ error: "节点执行世界未就绪"
116
+ });
117
+ return;
118
+ }
119
+ if (req.method === "GET" && action === "containers") {
120
+ sendJson(res, 200, {
121
+ ok: true,
122
+ containers: await listContainers(runner)
123
+ });
124
+ return;
125
+ }
126
+ if (req.method === "POST" && (action === "restart" || action === "stop" || action === "start" || action === "delete")) {
127
+ const containerId = node.docker?.containerId;
128
+ if (!containerId) {
129
+ sendJson(res, 400, {
130
+ ok: false,
131
+ error: "节点未关联容器"
132
+ });
133
+ return;
134
+ }
135
+ try {
136
+ if (action === "delete") {
137
+ const r = await runner.run([
138
+ "docker",
139
+ "rm",
140
+ "-f",
141
+ containerId
142
+ ]);
143
+ if (r.code !== 0) throw new Error((r.stderr || r.stdout).trim() || "删除容器失败");
144
+ sendJson(res, 200, {
145
+ ok: true,
146
+ action: "delete"
147
+ });
148
+ return;
149
+ }
150
+ if (action === "stop") {
151
+ const r = await runner.run([
152
+ "docker",
153
+ "stop",
154
+ containerId
155
+ ]);
156
+ if (r.code !== 0) throw new Error((r.stderr || r.stdout).trim() || "停止失败");
157
+ sendJson(res, 200, {
158
+ ok: true,
159
+ action: "stop"
160
+ });
161
+ return;
162
+ }
163
+ if (action === "start") {
164
+ const r = await runner.run([
165
+ "docker",
166
+ "start",
167
+ containerId
168
+ ]);
169
+ if (r.code !== 0) throw new Error((r.stderr || r.stdout).trim() || "启动失败");
170
+ sendJson(res, 200, {
171
+ ok: true,
172
+ action: "start"
173
+ });
174
+ return;
175
+ }
176
+ const inspect = await runner.run([
177
+ "docker",
178
+ "inspect",
179
+ "--format",
180
+ "{{.State.Running}}",
181
+ containerId
182
+ ]);
183
+ if (inspect.code !== 0) throw new Error("容器不存在或已删除");
184
+ const running = inspect.stdout.trim() === "true";
185
+ const cmd = running ? "restart" : "start";
186
+ const r = await runner.run([
187
+ "docker",
188
+ cmd,
189
+ containerId
190
+ ]);
191
+ if (r.code !== 0) throw new Error((r.stderr || r.stdout).trim() || "重启失败");
192
+ sendJson(res, 200, {
193
+ ok: true,
194
+ action: "restart",
195
+ was: running ? "running" : "stopped"
196
+ });
197
+ } catch (err) {
198
+ sendJson(res, 502, {
199
+ ok: false,
200
+ error: err instanceof Error ? err.message : String(err)
201
+ });
202
+ }
203
+ return;
204
+ }
205
+ sendJson(res, 404, {
206
+ ok: false,
207
+ error: "not found"
208
+ });
209
+ }
210
+ }));
211
+ }
212
+ //#endregion
213
+ export { registerDockerRoutes };
214
+
215
+ //# sourceMappingURL=docker-routes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"docker-routes.js","names":[],"sources":["../../src/exec-docker/docker-routes.ts"],"sourcesContent":["import type { IncomingMessage, ServerResponse } from 'node:http'\nimport type { Context } from '@deepseek-ai/cordis'\nimport type {} from '@deepseek-ai/dsh-host-webserver'\nimport { listContainers, provisionContainer } from './provision.js'\nimport { resolveHostRunner } from './host-runner.js'\n\nfunction sendJson(res: ServerResponse, status: number, body: unknown): void {\n res.writeHead(status, { 'content-type': 'application/json' })\n res.end(JSON.stringify(body))\n}\n\nfunction readBody(req: IncomingMessage): Promise<Record<string, unknown> | null> {\n return new Promise((resolve) => {\n if ((req.headers['content-type'] ?? '') !== 'application/json') {\n resolve(null)\n return\n }\n const chunks: Buffer[] = []\n let size = 0\n req.on('data', (chunk: Buffer) => {\n size += chunk.length\n if (size > 64 * 1024) {\n resolve(null)\n req.destroy()\n return\n }\n chunks.push(chunk)\n })\n req.on('end', () => {\n try {\n resolve(JSON.parse(Buffer.concat(chunks).toString('utf8')) as Record<string, unknown>)\n } catch {\n resolve(null)\n }\n })\n req.on('error', () => resolve(null))\n })\n}\n\nfunction pathSegments(req: IncomingMessage): string[] {\n return new URL(req.url ?? '/', 'http://x').pathname.split('/').filter(Boolean)\n}\n\ninterface NodeRegistryLike {\n get(id: string): { id: string; type: string; ssh?: unknown; docker?: { containerId?: string; image?: string; mode?: string; resources?: { cpus?: number; memoryMB?: number } } } | undefined\n}\n\nexport function registerDockerRoutes(ctx: Context): void {\n const webServer = ctx.webServer\n ctx.effect(() =>\n webServer.register({\n kind: 'prefix',\n path: '/api/dshb/docker',\n handler: async (req, res) => {\n const segs = pathSegments(req)\n const nodeId = segs[3]\n const action = segs[4]\n\n const nodeRegistry = ctx.get('nodeRegistry') as NodeRegistryLike | undefined\n const node = nodeId ? nodeRegistry?.get(nodeId) : undefined\n if (!node) {\n sendJson(res, 404, { ok: false, error: 'node not found' })\n return\n }\n\n if (req.method === 'POST' && action === 'provision') {\n const provisioner = ctx.get('dshbDockerProvisioner') as { provision(nodeId: string): Promise<{ containerId: string; name: string }> } | undefined\n if (provisioner) {\n try {\n const result = await provisioner.provision(nodeId)\n sendJson(res, 201, { ok: true, containerId: result.containerId, name: result.name })\n } catch (err) {\n sendJson(res, 502, { ok: false, error: err instanceof Error ? err.message : String(err) })\n }\n return\n }\n const body = await readBody(req)\n const image = String(body?.image ?? '')\n if (!image) {\n sendJson(res, 400, { ok: false, error: '需要 image' })\n return\n }\n const fallbackRunner = await resolveHostRunner(ctx, node)\n if (!fallbackRunner) {\n sendJson(res, 503, { ok: false, error: '节点执行世界未就绪' })\n return\n }\n try {\n const result = await provisionContainer(fallbackRunner, {\n image,\n cpus: body?.cpus ? Number(body.cpus) : undefined,\n memoryMB: body?.memoryMB ? Number(body.memoryMB) : undefined,\n name: body?.name ? String(body.name) : undefined,\n })\n sendJson(res, 201, { ok: true, containerId: result.containerId, name: result.name })\n } catch (err) {\n sendJson(res, 502, { ok: false, error: err instanceof Error ? err.message : String(err) })\n }\n return\n }\n\n const runner = await resolveHostRunner(ctx, node)\n\n if (!runner) {\n sendJson(res, 503, { ok: false, error: '节点执行世界未就绪' })\n return\n }\n\n if (req.method === 'GET' && action === 'containers') {\n const containers = await listContainers(runner)\n sendJson(res, 200, { ok: true, containers })\n return\n }\n\n if (req.method === 'POST' && (action === 'restart' || action === 'stop' || action === 'start' || action === 'delete')) {\n const containerId = node.docker?.containerId\n if (!containerId) {\n sendJson(res, 400, { ok: false, error: '节点未关联容器' })\n return\n }\n try {\n if (action === 'delete') {\n const r = await runner.run(['docker', 'rm', '-f', containerId])\n if (r.code !== 0) throw new Error((r.stderr || r.stdout).trim() || '删除容器失败')\n sendJson(res, 200, { ok: true, action: 'delete' })\n return\n }\n if (action === 'stop') {\n const r = await runner.run(['docker', 'stop', containerId])\n if (r.code !== 0) throw new Error((r.stderr || r.stdout).trim() || '停止失败')\n sendJson(res, 200, { ok: true, action: 'stop' })\n return\n }\n if (action === 'start') {\n const r = await runner.run(['docker', 'start', containerId])\n if (r.code !== 0) throw new Error((r.stderr || r.stdout).trim() || '启动失败')\n sendJson(res, 200, { ok: true, action: 'start' })\n return\n }\n const inspect = await runner.run(['docker', 'inspect', '--format', '{{.State.Running}}', containerId])\n if (inspect.code !== 0) throw new Error('容器不存在或已删除')\n const running = inspect.stdout.trim() === 'true'\n const cmd = running ? 'restart' : 'start'\n const r = await runner.run(['docker', cmd, containerId])\n if (r.code !== 0) throw new Error((r.stderr || r.stdout).trim() || '重启失败')\n sendJson(res, 200, { ok: true, action: 'restart', was: running ? 'running' : 'stopped' })\n } catch (err) {\n sendJson(res, 502, { ok: false, error: err instanceof Error ? err.message : String(err) })\n }\n return\n }\n\n sendJson(res, 404, { ok: false, error: 'not found' })\n },\n }),\n )\n}\n"],"mappings":"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"}
@@ -0,0 +1,50 @@
1
+ import { DockerBackend } from "./docker-backend.js";
2
+ //#region src/exec-docker/fs-provider.d.ts
3
+ declare class DockerFileSystem {
4
+ private readonly client;
5
+ constructor(client: DockerBackend);
6
+ resolve(path: string): Promise<string>;
7
+ processPath(target: string): string;
8
+ fileUrl(target: string): string;
9
+ contains(parent: string, child: string): boolean;
10
+ stat(target: string, _signal?: AbortSignal): Promise<{
11
+ size: number;
12
+ mtime: number;
13
+ isDirectory: boolean;
14
+ isFile: boolean;
15
+ } | undefined>;
16
+ lstat(path: string, _opts?: {
17
+ cwd?: string;
18
+ }, _signal?: AbortSignal): Promise<{
19
+ size: number;
20
+ mtime: number;
21
+ isDirectory: boolean;
22
+ isFile: boolean;
23
+ } | undefined>;
24
+ readText(target: string, _signal?: AbortSignal): Promise<string>;
25
+ streamText(target: string, _signal?: AbortSignal): Promise<AsyncIterable<string>>;
26
+ readBytes(target: string, _signal: AbortSignal | undefined, maxBytes: number): Promise<Uint8Array>;
27
+ listDir(target: string, _signal?: AbortSignal): Promise<Array<{
28
+ name: string;
29
+ path: string;
30
+ isDirectory: boolean;
31
+ isFile: boolean;
32
+ isSymlink: boolean;
33
+ size: number;
34
+ mtime: number;
35
+ }>>;
36
+ writeText(target: string, content: string, _expected?: unknown, _signal?: AbortSignal, _policy?: unknown): Promise<{
37
+ operation: 'create' | 'update';
38
+ version: string;
39
+ before: string | null;
40
+ after: string;
41
+ }>;
42
+ editText(target: string, edit: unknown, _expected?: unknown, _signal?: AbortSignal, _policy?: unknown): Promise<{
43
+ version: string;
44
+ before: string;
45
+ after: string;
46
+ }>;
47
+ }
48
+ //#endregion
49
+ export { DockerFileSystem };
50
+ //# sourceMappingURL=fs-provider.d.ts.map