hyperbrane 0.1.2 → 0.2.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 (287) hide show
  1. package/README.md +67 -25
  2. package/dist/agent/agentClient.d.ts +5 -0
  3. package/dist/agent/agentClient.js +4 -0
  4. package/dist/agent/agentService.d.ts +9 -37
  5. package/dist/agent/agentService.js +62 -322
  6. package/dist/agent/agentStorage.d.ts +1 -20
  7. package/dist/agent/agentStorage.js +0 -30
  8. package/dist/agent/api.d.ts +80 -151
  9. package/dist/agent/api.js +1 -8
  10. package/dist/agent/client.d.ts +3 -1
  11. package/dist/agent/client.js +2 -3
  12. package/dist/agent/control.d.ts +23 -0
  13. package/dist/agent/control.js +479 -0
  14. package/dist/agent/hostTranscript.d.ts +0 -6
  15. package/dist/agent/hostTranscript.js +3 -14
  16. package/dist/agent/session/backend.d.ts +17 -32
  17. package/dist/agent/session/claude/home.d.ts +0 -1
  18. package/dist/agent/session/claude/home.js +0 -1
  19. package/dist/agent/session/claude/provision.d.ts +10 -0
  20. package/dist/agent/session/claude/provision.js +17 -0
  21. package/dist/agent/session/claude/transcript.d.ts +2 -7
  22. package/dist/agent/session/claude/transcript.js +68 -14
  23. package/dist/agent/session/client.d.ts +1 -10
  24. package/dist/agent/session/client.js +0 -3
  25. package/dist/agent/session/codex/provision.d.ts +14 -0
  26. package/dist/agent/session/codex/provision.js +25 -0
  27. package/dist/agent/session/fileTranscript.d.ts +6 -49
  28. package/dist/agent/session/fileTranscript.js +26 -82
  29. package/dist/agent/session/install.d.ts +5 -0
  30. package/dist/agent/session/install.js +35 -0
  31. package/dist/agent/session/sessionStorage.d.ts +4 -35
  32. package/dist/agent/session/sessionStorage.js +10 -16
  33. package/dist/agent/session/sharedConnection.d.ts +0 -8
  34. package/dist/agent/session/sharedConnection.js +0 -7
  35. package/dist/agent/session/storedTranscripts.d.ts +1 -6
  36. package/dist/agent/session/storedTranscripts.js +0 -3
  37. package/dist/agent/session/worker/jsonRpc.d.ts +2 -13
  38. package/dist/agent/session/worker/jsonRpc.js +2 -11
  39. package/dist/agent/session/worker/protocol.d.ts +26 -20
  40. package/dist/agent/session/worker/protocol.js +29 -18
  41. package/dist/agent/session/worker/workerParams.d.ts +0 -5
  42. package/dist/agent/session/worker/workerParams.js +14 -14
  43. package/dist/agent/session/workerConnection.d.ts +2 -13
  44. package/dist/agent/session/workerConnection.js +7 -12
  45. package/dist/agent/session/workerGatewayConnection.d.ts +18 -0
  46. package/dist/agent/session/workerGatewayConnection.js +86 -0
  47. package/dist/agent/vendor-types/manifest.json +87 -0
  48. package/dist/agent/workerBundle.d.ts +4 -19
  49. package/dist/agent/workerBundle.generated.js +1 -1
  50. package/dist/agent/workerBundle.js +3 -30
  51. package/dist/agent/workerReplacement.d.ts +3 -0
  52. package/dist/agent/workerReplacement.js +49 -0
  53. package/dist/cli/agent.js +31 -16
  54. package/dist/cli/command.d.ts +10 -11
  55. package/dist/cli/command.js +0 -4
  56. package/dist/cli/dimension.js +6 -6
  57. package/dist/cli/gateway.js +3 -6
  58. package/dist/cli/main.js +14 -31
  59. package/dist/cli/streamPrinter.d.ts +2 -10
  60. package/dist/cli/streamPrinter.js +16 -46
  61. package/dist/cli/table.d.ts +0 -3
  62. package/dist/cli/table.js +0 -4
  63. package/dist/dimension/api.d.ts +39 -27
  64. package/dist/dimension/api.js +28 -47
  65. package/dist/dimension/client.d.ts +5 -0
  66. package/dist/dimension/client.js +4 -0
  67. package/dist/dimension/control.d.ts +21 -0
  68. package/dist/dimension/control.js +328 -0
  69. package/dist/dimension/image.d.ts +0 -5
  70. package/dist/dimension/image.js +0 -8
  71. package/dist/dimension/imageFiles.generated.js +1 -1
  72. package/dist/dimension/paths.d.ts +5 -4
  73. package/dist/dimension/paths.js +15 -6
  74. package/dist/dimension/service.d.ts +8 -24
  75. package/dist/dimension/service.js +60 -228
  76. package/dist/dimension/transport-worker.d.ts +1 -0
  77. package/dist/dimension/transport-worker.js +151 -0
  78. package/dist/dimension/transportBundle.d.ts +2 -0
  79. package/dist/dimension/transportBundle.generated.d.ts +1 -0
  80. package/dist/dimension/transportBundle.generated.js +2 -0
  81. package/dist/dimension/transportBundle.js +4 -0
  82. package/dist/dimension/transportClient.d.ts +10 -0
  83. package/dist/dimension/transportClient.js +115 -0
  84. package/dist/dimension/transportProtocol.d.ts +53 -0
  85. package/dist/dimension/transportProtocol.js +8 -0
  86. package/dist/index.d.ts +5 -9
  87. package/dist/index.js +5 -19
  88. package/dist/lib/control.d.ts +8 -0
  89. package/dist/lib/control.js +62 -0
  90. package/dist/lib/docker.d.ts +0 -22
  91. package/dist/lib/docker.js +0 -10
  92. package/dist/lib/image.d.ts +0 -3
  93. package/dist/lib/image.js +0 -2
  94. package/dist/lib/jsonDirectory.d.ts +0 -13
  95. package/dist/lib/jsonDirectory.js +10 -15
  96. package/dist/lib/scriptBundle.d.ts +6 -0
  97. package/dist/lib/scriptBundle.js +28 -0
  98. package/dist/lib/sh.d.ts +2 -0
  99. package/dist/lib/sh.js +8 -0
  100. package/dist/lib/sleep.d.ts +0 -1
  101. package/dist/lib/sleep.js +0 -1
  102. package/dist/lib/streamFetch.d.ts +15 -0
  103. package/dist/lib/streamFetch.js +150 -0
  104. package/dist/lib/tar.d.ts +0 -1
  105. package/dist/lib/tar.js +5 -11
  106. package/dist/portal-gateway/api.d.ts +13 -44
  107. package/dist/portal-gateway/api.js +1 -3
  108. package/dist/portal-gateway/control.d.ts +42 -0
  109. package/dist/portal-gateway/control.js +657 -0
  110. package/dist/portal-gateway/gatewayClient.d.ts +5 -0
  111. package/dist/portal-gateway/gatewayClient.js +4 -0
  112. package/dist/portal-gateway/gatewayService.d.ts +8 -38
  113. package/dist/portal-gateway/gatewayService.js +68 -254
  114. package/dist/portal-gateway/image.d.ts +0 -1
  115. package/dist/portal-gateway/image.js +0 -2
  116. package/dist/portal-gateway/imageFiles.generated.js +2 -2
  117. package/dist/portal-gateway/portalClient.d.ts +5 -0
  118. package/dist/portal-gateway/portalClient.js +4 -0
  119. package/dist/portal-gateway/portalService.d.ts +5 -47
  120. package/dist/portal-gateway/portalService.js +19 -349
  121. package/dist/portal-gateway/transport-worker.d.ts +1 -0
  122. package/dist/portal-gateway/transport-worker.js +205 -0
  123. package/dist/portal-gateway/transportBundle.d.ts +2 -0
  124. package/dist/portal-gateway/transportBundle.generated.d.ts +1 -0
  125. package/dist/portal-gateway/transportBundle.generated.js +2 -0
  126. package/dist/portal-gateway/transportBundle.js +4 -0
  127. package/dist/portal-gateway/upstream.d.ts +0 -81
  128. package/dist/portal-gateway/upstream.js +0 -1
  129. package/dist/sandbox/api.d.ts +121 -187
  130. package/dist/sandbox/api.js +5 -7
  131. package/dist/sandbox/client.d.ts +2 -0
  132. package/dist/sandbox/client.js +4 -0
  133. package/dist/sandbox/data-endpoints/dataEndpoints.d.ts +13 -0
  134. package/dist/sandbox/data-endpoints/dataEndpoints.js +100 -0
  135. package/dist/sandbox/data-endpoints/firewalledPorts.d.ts +4 -0
  136. package/dist/sandbox/data-endpoints/firewalledPorts.js +28 -0
  137. package/dist/sandbox/lib/httpOverStream.d.ts +3 -0
  138. package/dist/sandbox/lib/httpOverStream.js +21 -0
  139. package/dist/sandbox/lib/remoteFileOperations.d.ts +10 -0
  140. package/dist/sandbox/lib/remoteFileOperations.js +70 -0
  141. package/dist/sandbox/lib/shellCommand.d.ts +3 -0
  142. package/dist/sandbox/lib/shellCommand.js +7 -0
  143. package/dist/sandbox/providers/boat/boatService.d.ts +2 -0
  144. package/dist/sandbox/providers/boat/boatService.js +321 -0
  145. package/dist/sandbox/providers/exedev/exeDevService.d.ts +2 -0
  146. package/dist/sandbox/providers/exedev/exeDevService.js +127 -0
  147. package/dist/sandbox/providers/exedev/exeDevToken.d.ts +1 -0
  148. package/dist/sandbox/providers/exedev/exeDevToken.js +54 -0
  149. package/dist/sandbox/providers/local/localService.d.ts +2 -0
  150. package/dist/sandbox/providers/local/localService.js +219 -0
  151. package/dist/sandbox/providers/ssh/sshService.d.ts +2 -0
  152. package/dist/sandbox/providers/ssh/sshService.js +82 -0
  153. package/dist/sandbox/providers/ssh/sshTransport.d.ts +12 -0
  154. package/dist/sandbox/providers/ssh/sshTransport.js +216 -0
  155. package/dist/sandbox/service.d.ts +2 -0
  156. package/dist/sandbox/service.js +18 -0
  157. package/dist/sandbox/storage/descriptors.d.ts +6 -0
  158. package/dist/sandbox/storage/descriptors.js +25 -0
  159. package/dist/sandbox/storage/sandboxStorage.d.ts +11 -0
  160. package/dist/sandbox/storage/sandboxStorage.js +42 -0
  161. package/dist/upstreams/anthropic.d.ts +0 -5
  162. package/dist/upstreams/anthropic.js +1 -8
  163. package/dist/upstreams/index.d.ts +2 -6
  164. package/dist/upstreams/index.js +0 -4
  165. package/dist/upstreams/openai.d.ts +0 -5
  166. package/dist/upstreams/openai.js +1 -13
  167. package/package.json +12 -4
  168. package/dist/agent/gatewaySignIns.d.ts +0 -5
  169. package/dist/agent/gatewaySignIns.js +0 -11
  170. package/dist/agent/session/codex/home.d.ts +0 -2
  171. package/dist/agent/session/codex/home.js +0 -4
  172. package/dist/agent/session/codex/messages.d.ts +0 -30
  173. package/dist/agent/session/codex/messages.js +0 -135
  174. package/dist/agent/session/codex/transcript.d.ts +0 -12
  175. package/dist/agent/session/codex/transcript.js +0 -22
  176. package/dist/agent/session/sdkMessages.d.ts +0 -15
  177. package/dist/agent/session/sdkMessages.js +0 -28
  178. package/dist/agent/session/workerSseConnection.d.ts +0 -31
  179. package/dist/agent/session/workerSseConnection.js +0 -76
  180. package/dist/agent/vendor-types/codex/app-server/AbsolutePathBuf.d.ts +0 -10
  181. package/dist/agent/vendor-types/codex/app-server/AbsolutePathBuf.js +0 -2
  182. package/dist/agent/vendor-types/codex/app-server/FunctionCallOutputBody.d.ts +0 -2
  183. package/dist/agent/vendor-types/codex/app-server/FunctionCallOutputBody.js +0 -2
  184. package/dist/agent/vendor-types/codex/app-server/FunctionCallOutputContentItem.d.ts +0 -22
  185. package/dist/agent/vendor-types/codex/app-server/FunctionCallOutputContentItem.js +0 -2
  186. package/dist/agent/vendor-types/codex/app-server/ImageDetail.d.ts +0 -1
  187. package/dist/agent/vendor-types/codex/app-server/ImageDetail.js +0 -2
  188. package/dist/agent/vendor-types/codex/app-server/ImageGenerationFailure.d.ts +0 -5
  189. package/dist/agent/vendor-types/codex/app-server/ImageGenerationFailure.js +0 -2
  190. package/dist/agent/vendor-types/codex/app-server/ImageGenerationItem.d.ts +0 -11
  191. package/dist/agent/vendor-types/codex/app-server/ImageGenerationItem.js +0 -2
  192. package/dist/agent/vendor-types/codex/app-server/LegacyAppPathString.d.ts +0 -23
  193. package/dist/agent/vendor-types/codex/app-server/LegacyAppPathString.js +0 -2
  194. package/dist/agent/vendor-types/codex/app-server/MessagePhase.d.ts +0 -7
  195. package/dist/agent/vendor-types/codex/app-server/MessagePhase.js +0 -2
  196. package/dist/agent/vendor-types/codex/app-server/ReasoningEffort.d.ts +0 -4
  197. package/dist/agent/vendor-types/codex/app-server/ReasoningEffort.js +0 -2
  198. package/dist/agent/vendor-types/codex/app-server/SleepItem.d.ts +0 -7
  199. package/dist/agent/vendor-types/codex/app-server/SleepItem.js +0 -2
  200. package/dist/agent/vendor-types/codex/app-server/WebSearchItem.d.ts +0 -14
  201. package/dist/agent/vendor-types/codex/app-server/WebSearchItem.js +0 -2
  202. package/dist/agent/vendor-types/codex/app-server/serde_json/JsonValue.d.ts +0 -3
  203. package/dist/agent/vendor-types/codex/app-server/serde_json/JsonValue.js +0 -2
  204. package/dist/agent/vendor-types/codex/app-server/v2/AgentMessageDelivery.d.ts +0 -1
  205. package/dist/agent/vendor-types/codex/app-server/v2/AgentMessageDelivery.js +0 -2
  206. package/dist/agent/vendor-types/codex/app-server/v2/AsyncUserInputQuestion.d.ts +0 -4
  207. package/dist/agent/vendor-types/codex/app-server/v2/AsyncUserInputQuestion.js +0 -2
  208. package/dist/agent/vendor-types/codex/app-server/v2/ByteRange.d.ts +0 -4
  209. package/dist/agent/vendor-types/codex/app-server/v2/ByteRange.js +0 -2
  210. package/dist/agent/vendor-types/codex/app-server/v2/CollabAgentState.d.ts +0 -5
  211. package/dist/agent/vendor-types/codex/app-server/v2/CollabAgentState.js +0 -2
  212. package/dist/agent/vendor-types/codex/app-server/v2/CollabAgentStatus.d.ts +0 -1
  213. package/dist/agent/vendor-types/codex/app-server/v2/CollabAgentStatus.js +0 -2
  214. package/dist/agent/vendor-types/codex/app-server/v2/CollabAgentTool.d.ts +0 -1
  215. package/dist/agent/vendor-types/codex/app-server/v2/CollabAgentTool.js +0 -2
  216. package/dist/agent/vendor-types/codex/app-server/v2/CollabAgentToolCallStatus.d.ts +0 -1
  217. package/dist/agent/vendor-types/codex/app-server/v2/CollabAgentToolCallStatus.js +0 -2
  218. package/dist/agent/vendor-types/codex/app-server/v2/CommandAction.d.ts +0 -19
  219. package/dist/agent/vendor-types/codex/app-server/v2/CommandAction.js +0 -2
  220. package/dist/agent/vendor-types/codex/app-server/v2/CommandExecutionSource.d.ts +0 -1
  221. package/dist/agent/vendor-types/codex/app-server/v2/CommandExecutionSource.js +0 -2
  222. package/dist/agent/vendor-types/codex/app-server/v2/CommandExecutionStatus.d.ts +0 -1
  223. package/dist/agent/vendor-types/codex/app-server/v2/CommandExecutionStatus.js +0 -2
  224. package/dist/agent/vendor-types/codex/app-server/v2/DynamicToolCallOutputContentItem.d.ts +0 -10
  225. package/dist/agent/vendor-types/codex/app-server/v2/DynamicToolCallOutputContentItem.js +0 -2
  226. package/dist/agent/vendor-types/codex/app-server/v2/DynamicToolCallStatus.d.ts +0 -1
  227. package/dist/agent/vendor-types/codex/app-server/v2/DynamicToolCallStatus.js +0 -2
  228. package/dist/agent/vendor-types/codex/app-server/v2/FileUpdateChange.d.ts +0 -6
  229. package/dist/agent/vendor-types/codex/app-server/v2/FileUpdateChange.js +0 -2
  230. package/dist/agent/vendor-types/codex/app-server/v2/HookPromptFragment.d.ts +0 -4
  231. package/dist/agent/vendor-types/codex/app-server/v2/HookPromptFragment.js +0 -2
  232. package/dist/agent/vendor-types/codex/app-server/v2/McpAppDisplayMode.d.ts +0 -1
  233. package/dist/agent/vendor-types/codex/app-server/v2/McpAppDisplayMode.js +0 -2
  234. package/dist/agent/vendor-types/codex/app-server/v2/McpAppUi.d.ts +0 -8
  235. package/dist/agent/vendor-types/codex/app-server/v2/McpAppUi.js +0 -2
  236. package/dist/agent/vendor-types/codex/app-server/v2/McpToolCallAppContext.d.ts +0 -7
  237. package/dist/agent/vendor-types/codex/app-server/v2/McpToolCallAppContext.js +0 -2
  238. package/dist/agent/vendor-types/codex/app-server/v2/McpToolCallError.d.ts +0 -3
  239. package/dist/agent/vendor-types/codex/app-server/v2/McpToolCallError.js +0 -2
  240. package/dist/agent/vendor-types/codex/app-server/v2/McpToolCallResult.d.ts +0 -6
  241. package/dist/agent/vendor-types/codex/app-server/v2/McpToolCallResult.js +0 -2
  242. package/dist/agent/vendor-types/codex/app-server/v2/McpToolCallStatus.d.ts +0 -1
  243. package/dist/agent/vendor-types/codex/app-server/v2/McpToolCallStatus.js +0 -2
  244. package/dist/agent/vendor-types/codex/app-server/v2/MemoryCitation.d.ts +0 -5
  245. package/dist/agent/vendor-types/codex/app-server/v2/MemoryCitation.js +0 -2
  246. package/dist/agent/vendor-types/codex/app-server/v2/MemoryCitationEntry.d.ts +0 -6
  247. package/dist/agent/vendor-types/codex/app-server/v2/MemoryCitationEntry.js +0 -2
  248. package/dist/agent/vendor-types/codex/app-server/v2/PatchApplyStatus.d.ts +0 -1
  249. package/dist/agent/vendor-types/codex/app-server/v2/PatchApplyStatus.js +0 -2
  250. package/dist/agent/vendor-types/codex/app-server/v2/PatchChangeKind.d.ts +0 -8
  251. package/dist/agent/vendor-types/codex/app-server/v2/PatchChangeKind.js +0 -2
  252. package/dist/agent/vendor-types/codex/app-server/v2/SubAgentActivityKind.d.ts +0 -1
  253. package/dist/agent/vendor-types/codex/app-server/v2/SubAgentActivityKind.js +0 -2
  254. package/dist/agent/vendor-types/codex/app-server/v2/TextElement.d.ts +0 -11
  255. package/dist/agent/vendor-types/codex/app-server/v2/TextElement.js +0 -2
  256. package/dist/agent/vendor-types/codex/app-server/v2/ThreadItem.d.ts +0 -215
  257. package/dist/agent/vendor-types/codex/app-server/v2/ThreadItem.js +0 -2
  258. package/dist/agent/vendor-types/codex/app-server/v2/UserInput.d.ts +0 -35
  259. package/dist/agent/vendor-types/codex/app-server/v2/UserInput.js +0 -2
  260. package/dist/agent/vendor-types/codex/app-server/v2/WebSearchAction.d.ts +0 -14
  261. package/dist/agent/vendor-types/codex/app-server/v2/WebSearchAction.js +0 -2
  262. package/dist/cli/services.d.ts +0 -19
  263. package/dist/cli/services.js +0 -34
  264. package/dist/dimension/http.d.ts +0 -7
  265. package/dist/dimension/http.js +0 -77
  266. package/dist/dimension/storage.d.ts +0 -39
  267. package/dist/dimension/storage.js +0 -79
  268. package/dist/portal-gateway/fakes.d.ts +0 -3
  269. package/dist/portal-gateway/fakes.js +0 -11
  270. package/dist/portal-gateway/gatewayStorage.d.ts +0 -66
  271. package/dist/portal-gateway/gatewayStorage.js +0 -125
  272. package/dist/portal-gateway/portalStorage.d.ts +0 -34
  273. package/dist/portal-gateway/portalStorage.js +0 -44
  274. package/dist/sandbox/boatService.d.ts +0 -52
  275. package/dist/sandbox/boatService.js +0 -343
  276. package/dist/sandbox/containerService.d.ts +0 -15
  277. package/dist/sandbox/containerService.js +0 -184
  278. package/dist/sandbox/endpointProxy.d.ts +0 -90
  279. package/dist/sandbox/endpointProxy.js +0 -716
  280. package/dist/sandbox/exeDevAuth.d.ts +0 -16
  281. package/dist/sandbox/exeDevAuth.js +0 -49
  282. package/dist/sandbox/localService.d.ts +0 -19
  283. package/dist/sandbox/localService.js +0 -200
  284. package/dist/sandbox/sseConnection.d.ts +0 -41
  285. package/dist/sandbox/sseConnection.js +0 -166
  286. package/dist/sandbox/sshService.d.ts +0 -30
  287. package/dist/sandbox/sshService.js +0 -680
@@ -1,343 +0,0 @@
1
- import { createHash } from "node:crypto";
2
- import { posix } from "node:path";
3
- import { BoatApi, Configuration, ResponseError, waitUntilReady } from "@boatdev/sdk";
4
- import { SandboxEndpointError, SandboxNotFoundError, } from "./api.js";
5
- import { createProxyEndpoints, proxyCredentialsAuth } from "./endpointProxy.js";
6
- import { endpointRequest, openSseConnection } from "./sseConnection.js";
7
- /** What the scripts below exit with for a missing path, and for one that exists already. */
8
- const NOT_FOUND = 44;
9
- const EXISTS = 45;
10
- /** Boat's cap on a command. */
11
- const COMMAND_TIMEOUT_S = 600;
12
- /** The port the sandbox's endpoint proxy listens on, published by Boat. */
13
- const DEFAULT_PROXY_PORT = 8900;
14
- /** States a sandbox runs commands in; stopped and archived ones are resumed. */
15
- const RUNNING_STATES = ["ready", "idle", "running"];
16
- export const BOAT_API_URL = "https://boat.dev/api/v1";
17
- /** Each sandbox's own endpoints (createBoatSandbox's socketEndpoint), by sandbox id, for the life of the process. */
18
- const OWN_ENDPOINTS = new Map();
19
- export function createBoatApi(apiKey, basePath = BOAT_API_URL) {
20
- return new BoatApi(new Configuration({ basePath, accessToken: apiKey }));
21
- }
22
- /** A call to Boat that failed: its HTTP status and Boat's error code. */
23
- export class BoatError extends Error {
24
- status;
25
- code;
26
- name = "BoatError";
27
- constructor(status, code, message) {
28
- super(message);
29
- this.status = status;
30
- this.code = code;
31
- }
32
- }
33
- async function boat(what, call) {
34
- try {
35
- return await call();
36
- }
37
- catch (error) {
38
- if (!(error instanceof ResponseError))
39
- throw error;
40
- const text = await error.response.text().catch(() => "");
41
- let code = "";
42
- let message = text;
43
- try {
44
- const body = JSON.parse(text);
45
- code = body.code ?? "";
46
- message = body.message ?? text;
47
- }
48
- catch { }
49
- throw new BoatError(error.response.status, code, `${what} failed (${error.response.status}${code ? ` ${code}` : ""}): ${message}`);
50
- }
51
- }
52
- /**
53
- * A Boat sandbox (https://boat.dev), driven through Boat's API alone (its SDK): files through its
54
- * file calls, commands through its command call (one shell line, up to 600s), and what needs a
55
- * stream into the sandbox through the sandbox's own endpoints: `connect` is an "sse" endpoint to
56
- * the socket, `fetch` an "http" one (so docker's responses stream). Those endpoints, like any the
57
- * sandbox exposes, sit behind one proxy that Boat publishes on a URL of its own (boatHostPortAuth).
58
- * Sandbox paths are paths in the sandbox; the sandbox must be running (ensureBoatRunning).
59
- */
60
- export function createBoatSandbox(api, sandboxId, { proxyPort = DEFAULT_PROXY_PORT, stateDirectory } = {}) {
61
- const where = `Boat sandbox ${sandboxId}`;
62
- function full(path) {
63
- return posix.resolve("/", path);
64
- }
65
- /** Runs `command` (one shell line) and returns what it printed; throws if Boat cut its output. */
66
- async function run(command) {
67
- const result = await boat(`a command in ${where}`, () => api.command({ sandboxId, command, timeoutSeconds: COMMAND_TIMEOUT_S }));
68
- if (!("exitCode" in result))
69
- throw new Error(`a command in ${where} was started in the background`);
70
- if (result.timedOut)
71
- throw new Error(`a command in ${where} ran past ${COMMAND_TIMEOUT_S}s`);
72
- if (result.stdoutTruncated || result.stderrTruncated)
73
- throw new Error(`Boat cut the output of a command in ${where} short`);
74
- return { exitCode: result.exitCode ?? -1, stdout: result.stdout ?? "", stderr: result.stderr ?? "" };
75
- }
76
- /** `script` in sh, with `args` as $1, $2, ... */
77
- function sh(script, ...args) {
78
- return run(["sh", "-c", script, "sh", ...args].map(quote).join(" "));
79
- }
80
- function check(result, what, path) {
81
- if (result.exitCode === NOT_FOUND)
82
- throw new SandboxNotFoundError(`Not found: "${path}"`);
83
- if (result.exitCode !== 0)
84
- throw new Error(`${what} ${path} in ${where} failed (exit ${result.exitCode}): ${result.stderr.trim()}`);
85
- }
86
- /** Puts `data` in a new file under /tmp through Boat's file call and returns its path. */
87
- async function upload(data) {
88
- const path = `/tmp/.hyb-${crypto.randomUUID()}`;
89
- const content = Buffer.from(typeof data === "string" ? new TextEncoder().encode(data) : data).toString("base64");
90
- await boat(`writing to ${where}`, () => api.writeFile({ sandboxId, path, content, encoding: "base64" }));
91
- return path;
92
- }
93
- async function readFile(path) {
94
- const target = full(path);
95
- try {
96
- const read = await boat(`reading ${target} in ${where}`, () => api.readFile({ sandboxId, path: target, encoding: "base64" }));
97
- return new Uint8Array(Buffer.from(read.content ?? "", "base64"));
98
- }
99
- catch (error) {
100
- // Boat passes the sandbox's own error on: 400 sandbox_direct_failed, "ENOENT: ...".
101
- if (error instanceof BoatError && /ENOENT/.test(error.message))
102
- throw new SandboxNotFoundError(`Not found: "${path}"`);
103
- throw error;
104
- }
105
- }
106
- // Boat's file call reads whole files only.
107
- async function readFileRange(path, start, end) {
108
- const target = full(path);
109
- const result = await sh(`[ -e "$1" ] || exit ${NOT_FOUND}; tail -c +$(($2 + 1)) -- "$1" | head -c "$3" | base64 -w0`, target, String(start), String(Math.max(0, end - start)));
110
- check(result, "read", target);
111
- return new Uint8Array(Buffer.from(result.stdout, "base64"));
112
- }
113
- /**
114
- * Uploads `data`, copies it beside `target` (creating its directory) and runs `publish` on the copy
115
- * ($3) and the target ($2), so readers never see part of it.
116
- */
117
- async function place(target, data, publish) {
118
- const uploaded = await upload(data);
119
- const beside = posix.join(posix.dirname(target), `.${posix.basename(target)}.${crypto.randomUUID()}.tmp`);
120
- return sh(`mkdir -p -- "$(dirname -- "$2")" && cp -- "$1" "$3" || { rm -f -- "$1" "$3"; exit 1; }\nrm -f -- "$1"\n${publish}`, uploaded, target, beside);
121
- }
122
- async function writeFile(path, data) {
123
- const target = full(path);
124
- check(await place(target, data, 'mv -f -- "$3" "$2" || { rm -f -- "$3"; exit 1; }'), "write", target);
125
- }
126
- // A hard link refuses to replace an existing file.
127
- async function writeFileExclusive(path, data) {
128
- const target = full(path);
129
- const result = await place(target, data, `if ln -- "$3" "$2" 2>/dev/null; then rm -f -- "$3"; exit 0; fi; rm -f -- "$3"; [ -e "$2" ] && exit ${EXISTS}; exit 1`);
130
- if (result.exitCode === EXISTS)
131
- return false;
132
- check(result, "write", target);
133
- return true;
134
- }
135
- async function removeFile(path) {
136
- check(await sh('rm -f -- "$1"', full(path)), "remove", full(path));
137
- }
138
- async function createDirectory(path) {
139
- check(await sh('mkdir -p -- "$1"', full(path)), "mkdir", full(path));
140
- }
141
- async function removeDirectory(path) {
142
- check(await sh('rm -rf -- "$1"', full(path)), "remove", full(path));
143
- }
144
- async function listDirectory(path) {
145
- const result = await sh(`[ -e "$1" ] || exit ${NOT_FOUND}; ls -A1 -- "$1"`, full(path));
146
- check(result, "list", full(path));
147
- return result.stdout.split("\n").filter(Boolean);
148
- }
149
- async function stat(path) {
150
- const result = await sh(`[ -e "$1" ] || exit ${NOT_FOUND}; stat -c '%u:%g:%s:%F' -- "$1"`, full(path));
151
- check(result, "stat", full(path));
152
- const [uid = "", gid = "", size = "", kind = ""] = result.stdout.trim().split(":");
153
- const type = kind === "directory" ? "directory" : kind.startsWith("regular") ? "file" : "other";
154
- return { type, uid: Number(uid), gid: Number(gid), size: Number(size) };
155
- }
156
- let home = null;
157
- function homeDirectory() {
158
- home ??= sh('printf %s "$HOME"').then((result) => {
159
- check(result, "home of", "the user");
160
- return result.stdout || "/";
161
- });
162
- home.catch(() => (home = null));
163
- return home;
164
- }
165
- // stdin goes up as a file first: Boat's command call takes none.
166
- async function runCommand(cmd, { cwd, env = {}, stdin } = {}) {
167
- const input = stdin === undefined ? null : await upload(stdin);
168
- const words = ["env", ...Object.entries(env).map(([name, value]) => `${name}=${value}`), ...cmd].map(quote).join(" ");
169
- const enter = cwd === undefined ? "" : `cd ${quote(full(cwd))} && `;
170
- const line = `${enter}${words} < ${input === null ? "/dev/null" : quote(input)}`;
171
- return run(["sh", "-c", input === null ? line : `${line}; status=$?; rm -f -- ${quote(input)}; exit $status`].map(quote).join(" "));
172
- }
173
- const endpoints = createProxyEndpoints({ runCommand }, { accessibleHost: `${sandboxId}.on.boat.dev:${proxyPort}`, auth: boatHostPortAuth(api, sandboxId), stateDirectory }, where);
174
- // The sandbox's own endpoints to its sockets, by socket and transport: the same record (and URL)
175
- // whichever sandbox object asks, so they don't pile up, and held by the process (OWN_ENDPOINTS) so
176
- // a sandbox object made for each request doesn't set them up again; exposed again when they stop
177
- // answering.
178
- function socketEndpoint(path, transport, fresh = false) {
179
- const key = `${transport}:${path}`;
180
- const ownEndpoints = OWN_ENDPOINTS.get(sandboxId) ?? new Map();
181
- OWN_ENDPOINTS.set(sandboxId, ownEndpoints);
182
- if (fresh)
183
- ownEndpoints.delete(key);
184
- let endpoint = ownEndpoints.get(key);
185
- if (!endpoint) {
186
- const request = { target: { unix: path }, scheme: "bearer", transport };
187
- endpoint = endpoints.exposeEndpointAs(nameFor(key), request);
188
- endpoint.catch(() => ownEndpoints.delete(key));
189
- ownEndpoints.set(key, endpoint);
190
- }
191
- return endpoint;
192
- }
193
- /**
194
- * The endpoint to retry exposing: Boat's front answers 502-504 for a proxy that isn't listening (a
195
- * resume restarted the machine), the proxy 401 for credentials since replaced (docker never does).
196
- */
197
- const stale = (status) => status === 401 || status === 502 || status === 503 || status === 504;
198
- async function fetchInside(url, { unix, signal, timeout: _timeout, ...init } = {}) {
199
- if (unix === undefined)
200
- throw new Error("the Boat sandbox fetches only over unix sockets (init.unix)");
201
- const target = new URL(url);
202
- const path = full(unix);
203
- const attempt = async (fresh) => {
204
- const [requestUrl, requestInit] = endpointRequest(await socketEndpoint(path, "http", fresh), `${target.pathname}${target.search}`, { ...init, signal });
205
- return fetch(requestUrl, requestInit);
206
- };
207
- try {
208
- const response = await attempt(false);
209
- if (!stale(response.status))
210
- return response;
211
- await response.body?.cancel();
212
- }
213
- catch (error) {
214
- if (signal?.aborted)
215
- throw error;
216
- }
217
- return attempt(true);
218
- }
219
- async function connect(path) {
220
- const socket = full(path);
221
- try {
222
- return await openSseConnection(await socketEndpoint(socket, "sse"));
223
- }
224
- catch {
225
- return openSseConnection(await socketEndpoint(socket, "sse", true));
226
- }
227
- }
228
- // Boat archives the sandbox when its TTL runs out (60 minutes after creation unless set).
229
- async function getExpiration() {
230
- const { sandbox } = await boat(`reading ${where}`, () => api.get({ sandboxId }));
231
- return sandbox.archiveAfter ? new Date(sandbox.archiveAfter) : null;
232
- }
233
- async function setExpiration(seconds) {
234
- if (seconds !== null && !(Number.isInteger(seconds) && seconds > 0))
235
- throw new Error(`not a number of seconds: ${seconds}`);
236
- const { sandbox } = await boat(`setting when ${where} stops`, () => api.update({ sandboxId, ttlSeconds: seconds }));
237
- return sandbox.archiveAfter ? new Date(sandbox.archiveAfter) : null;
238
- }
239
- return {
240
- getExpiration,
241
- setExpiration,
242
- readFile,
243
- readFileRange,
244
- writeFile,
245
- writeFileExclusive,
246
- removeFile,
247
- createDirectory,
248
- removeDirectory,
249
- listDirectory,
250
- stat,
251
- homeDirectory,
252
- runCommand,
253
- fetch: fetchInside,
254
- connect,
255
- ...endpoints,
256
- };
257
- }
258
- /**
259
- * Endpoints published by Boat: the proxy listens on the accessible host's port inside the sandbox
260
- * and checks the bearer or basic credentials it mints per endpoint (proxyCredentialsAuth), and
261
- * Boat's hostPort makes that port public, with Boat's TLS, on a URL of its own:
262
- * https://<sandbox>-<port>.on.boat.dev. Boat's own guard for a port (a token it trades for a
263
- * cookie) isn't used: a client can't carry it through a POST, nor a browser keep it across sites.
264
- */
265
- export function boatHostPortAuth(api, sandboxId) {
266
- return {
267
- bind(accessible) {
268
- if (accessible.port === null)
269
- throw new SandboxEndpointError("Boat endpoints need a fixed port: the one Boat publishes");
270
- const own = proxyCredentialsAuth().bind(accessible);
271
- let published = null;
272
- const publish = (port) => {
273
- published ??= boat(`publishing port ${port} of Boat sandbox ${sandboxId}`, () => api.hostPort({ sandboxId, port, _public: true })).then((hosted) => new URL(hosted.url ?? "").origin);
274
- published.catch(() => (published = null));
275
- return published;
276
- };
277
- return {
278
- // Boat opens the sandbox's firewall for the port itself.
279
- firewall: false,
280
- plan(request) {
281
- const plan = own.plan(request);
282
- return {
283
- ...plan,
284
- publish: async (port) => ({ url: await publish(port), credentials: (await plan.publish(port)).credentials }),
285
- };
286
- },
287
- };
288
- },
289
- };
290
- }
291
- /** A UUID-shaped name of `key`'s own, for an endpoint the sandbox keeps for itself. */
292
- function nameFor(key) {
293
- const hex = createHash("sha256").update(key).digest("hex");
294
- return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;
295
- }
296
- /** Quotes a word for a POSIX shell. */
297
- function quote(word) {
298
- return `'${word.replaceAll("'", `'\\''`)}'`;
299
- }
300
- /** Creates a sandbox, names it, and waits until it runs commands. */
301
- export async function createBoatMachine(api, { name }) {
302
- const created = await boat("creating a Boat sandbox", () => api.create({}));
303
- const sandboxId = created.sandbox.id;
304
- await boat(`naming Boat sandbox ${sandboxId}`, () => api.update({ sandboxId, name }));
305
- return waitUntilReady(api, sandboxId, { timeoutMs: 180_000 });
306
- }
307
- /** The sandbox, running: one that was stopped (archived) is resumed, its disk as it was, first. */
308
- export async function ensureBoatRunning(api, sandboxId) {
309
- const deadline = Date.now() + 180_000;
310
- for (;;) {
311
- const { sandbox } = await boat(`reading Boat sandbox ${sandboxId}`, () => api.get({ sandboxId }));
312
- if (RUNNING_STATES.includes(sandbox.state))
313
- return sandbox;
314
- if (sandbox.state === "archived") {
315
- await boat(`resuming Boat sandbox ${sandboxId}`, () => api.resume({ sandboxId }));
316
- return waitUntilReady(api, sandboxId, { timeoutMs: Math.max(1, deadline - Date.now()) });
317
- }
318
- if (sandbox.state === "error" || sandbox.state === "cancelled") {
319
- throw new Error(`Boat sandbox ${sandboxId} is ${sandbox.state}${sandbox.error ? `: ${sandbox.error}` : ""}`);
320
- }
321
- // Starting, or still being archived: it's resumed once that's done.
322
- if (Date.now() > deadline)
323
- throw new Error(`Boat sandbox ${sandboxId} stayed ${sandbox.state}`);
324
- await new Promise((resolve) => setTimeout(resolve, 2000));
325
- }
326
- }
327
- /** Stops the sandbox, saving its disk first (Boat archives it), for a later ensureBoatRunning. */
328
- export async function archiveBoatMachine(api, sandboxId) {
329
- await boat(`stopping Boat sandbox ${sandboxId}`, () => api.stop({ sandboxId }));
330
- }
331
- /**
332
- * Lets `publicKey` (OpenSSH) log in to the running sandbox, and says where: Boat's SSH endpoint for
333
- * it and its user. The endpoint may change when the sandbox is resumed on another machine.
334
- */
335
- export async function authorizeBoatSshKey(api, sandboxId, publicKey) {
336
- const response = await boat(`adding an SSH key to Boat sandbox ${sandboxId}`, () => api.sshKey({ sandboxId, key: publicKey }));
337
- const endpoint = /^(.+):(\d+)$/.exec(response.sshEndpoint ?? "");
338
- if (endpoint)
339
- return { host: endpoint[1], port: Number(endpoint[2]), user: response.sshUser ?? "user" };
340
- if (response.machineIp)
341
- return { host: response.machineIp, port: 22, user: response.sshUser ?? "user" };
342
- throw new Error(`Boat gave no SSH endpoint for sandbox ${sandboxId}`);
343
- }
@@ -1,15 +0,0 @@
1
- import type { DockerClient } from "../lib/docker.ts";
2
- import { type Sandbox } from "./api.ts";
3
- export type ContainerSandboxOptions = {
4
- /** "uid:gid", a user name or "root"; defaults to the container's own user. */
5
- user?: string;
6
- };
7
- /**
8
- * Runs inside a running container through the docker API: every operation is a `docker exec` as
9
- * `user`, and files are written by extracting an archive, so the container needs nothing but a
10
- * POSIX shell and coreutils (fetch also needs curl). Sandbox paths are container paths.
11
- *
12
- * `fetch` runs curl in the container and returns once the whole response has arrived: fine for
13
- * requests and long polls, not for streams. Its `signal` is only checked before the request starts.
14
- */
15
- export declare function createContainerSandbox(docker: DockerClient, container: string, { user }?: ContainerSandboxOptions): Sandbox;
@@ -1,184 +0,0 @@
1
- import { posix } from "node:path";
2
- import { SandboxNotFoundError, } from "./api.js";
3
- /** What the scripts below exit with for a missing path. */
4
- const NOT_FOUND = 44;
5
- /** writeFileExclusive's script: the file already exists. */
6
- const EXISTS = 45;
7
- /**
8
- * Runs inside a running container through the docker API: every operation is a `docker exec` as
9
- * `user`, and files are written by extracting an archive, so the container needs nothing but a
10
- * POSIX shell and coreutils (fetch also needs curl). Sandbox paths are container paths.
11
- *
12
- * `fetch` runs curl in the container and returns once the whole response has arrived: fine for
13
- * requests and long polls, not for streams. Its `signal` is only checked before the request starts.
14
- */
15
- export function createContainerSandbox(docker, container, { user } = {}) {
16
- let owner;
17
- function containerPath(path) {
18
- return posix.resolve("/", path);
19
- }
20
- async function sh(script, env = {}, options = {}) {
21
- return docker.exec(container, { cmd: ["sh", "-c", script], user: options.user ?? user, env, workdir: options.workdir });
22
- }
23
- /** Throws SandboxNotFoundError for the scripts' NOT_FOUND, and an Error for any other failure. */
24
- function check(result, what, path) {
25
- if (result.exitCode === NOT_FOUND)
26
- throw new SandboxNotFoundError(`Not found: "${path}"`);
27
- if (result.exitCode !== 0)
28
- throw new Error(`${what} ${path} in ${container} failed: ${result.stderr.trim()}`);
29
- return result;
30
- }
31
- /** "uid:gid" of the sandbox user, which files written through an archive are handed to. */
32
- function fileOwner() {
33
- owner ??= sh('printf "%s:%s" "$(id -u)" "$(id -g)"').then((result) => check(result, "id", "").stdout.trim());
34
- return owner;
35
- }
36
- async function readFile(path) {
37
- const full = containerPath(path);
38
- const result = check(await sh(`[ -e "$P" ] || exit ${NOT_FOUND}; base64 -w0 -- "$P"`, { P: full }), "read", full);
39
- return new Uint8Array(Buffer.from(result.stdout, "base64"));
40
- }
41
- async function readFileRange(path, start, end) {
42
- const full = containerPath(path);
43
- const script = `[ -e "$P" ] || exit ${NOT_FOUND}; tail -c +$(($S + 1)) -- "$P" | head -c "$L" | base64 -w0`;
44
- const result = check(await sh(script, { P: full, S: String(start), L: String(Math.max(0, end - start)) }), "read", full);
45
- return new Uint8Array(Buffer.from(result.stdout, "base64"));
46
- }
47
- /**
48
- * Extracts `data` next to `full` under a unique hidden name, handed to the sandbox user, and runs
49
- * `publish` (as root, with $T the temp file and $F the target) to move it into place.
50
- */
51
- async function writeVia(full, data, publish) {
52
- const dir = posix.dirname(full);
53
- const tmp = `.${posix.basename(full)}.${crypto.randomUUID()}.tmp`;
54
- check(await sh('mkdir -p -- "$D"', { D: dir }), "mkdir", dir);
55
- await docker.putArchive(container, dir, { [tmp]: data });
56
- return sh(`chown "$OWNER" -- "$T" && ${publish}`, { OWNER: await fileOwner(), T: `${dir}/${tmp}`, F: full }, { user: "root" });
57
- }
58
- async function writeFile(path, data) {
59
- const full = containerPath(path);
60
- check(await writeVia(full, data, 'mv -f -- "$T" "$F"'), "write", full);
61
- }
62
- // ln refuses to replace an existing file, and publishes the fully written temp file.
63
- async function writeFileExclusive(path, data) {
64
- const full = containerPath(path);
65
- const result = await writeVia(full, data, `if ln -- "$T" "$F" 2>/dev/null; then rm -f -- "$T"; exit 0; fi; rm -f -- "$T"; [ -e "$F" ] && exit ${EXISTS}; exit 1`);
66
- if (result.exitCode === EXISTS)
67
- return false;
68
- check(result, "write", full);
69
- return true;
70
- }
71
- async function removeFile(path) {
72
- const full = containerPath(path);
73
- check(await sh('rm -f -- "$P"', { P: full }), "remove", full);
74
- }
75
- async function createDirectory(path) {
76
- const full = containerPath(path);
77
- check(await sh('mkdir -p -- "$P"', { P: full }), "mkdir", full);
78
- }
79
- async function removeDirectory(path) {
80
- const full = containerPath(path);
81
- check(await sh('rm -rf -- "$P"', { P: full }), "remove", full);
82
- }
83
- async function listDirectory(path) {
84
- const full = containerPath(path);
85
- const result = check(await sh(`[ -e "$P" ] || exit ${NOT_FOUND}; ls -A1 -- "$P"`, { P: full }), "list", full);
86
- return result.stdout.split("\n").filter(Boolean);
87
- }
88
- async function stat(path) {
89
- const full = containerPath(path);
90
- const result = check(await sh(`[ -e "$P" ] || exit ${NOT_FOUND}; stat -c '%u:%g:%s:%F' -- "$P"`, { P: full }), "stat", full);
91
- const [uid = "", gid = "", size = "", kind = ""] = result.stdout.trim().split(":");
92
- const type = kind === "directory" ? "directory" : kind.startsWith("regular") ? "file" : "other";
93
- return { type, uid: Number(uid), gid: Number(gid), size: Number(size) };
94
- }
95
- // docker exec doesn't always set HOME for a numeric user, so ask the user database.
96
- async function homeDirectory() {
97
- const result = check(await sh('getent passwd "$(id -u)" | cut -d: -f6'), "home of", user ?? "the container user");
98
- return result.stdout.trim() || "/";
99
- }
100
- // docker exec has no stdin here, so input travels base64-encoded in the environment.
101
- async function runCommand(cmd, { cwd, env = {}, stdin } = {}) {
102
- const workdir = cwd === undefined ? undefined : containerPath(cwd);
103
- if (stdin === undefined)
104
- return docker.exec(container, { cmd, user, env, workdir });
105
- return docker.exec(container, {
106
- cmd: ["sh", "-c", 'printf %s "$HYB_STDIN" | base64 -d | "$@"', "sh", ...cmd],
107
- user,
108
- env: { ...env, HYB_STDIN: Buffer.from(stdin).toString("base64") },
109
- workdir,
110
- });
111
- }
112
- async function fetchInContainer(url, init = {}) {
113
- if (init.signal?.aborted)
114
- throw init.signal.reason;
115
- const args = ["-sS", "-i", "-X", init.method ?? "GET", "-H", "Expect:"];
116
- for (const [name, value] of Object.entries(init.headers ?? {}))
117
- args.push("-H", `${name}: ${value}`);
118
- if (init.unix !== undefined)
119
- args.push("--unix-socket", containerPath(init.unix));
120
- // An idle timeout: give up once fewer than 1 byte a second has moved for that long.
121
- if (typeof init.timeout === "number")
122
- args.push("--speed-limit", "1", "--speed-time", String(Math.ceil(init.timeout / 1000)));
123
- if (init.body !== undefined)
124
- args.push("--data-binary", "@-");
125
- args.push(url);
126
- // The response goes through a file and out as base64, so binary bodies survive and curl's own
127
- // exit code is still the script's.
128
- const script = [
129
- 't="$(mktemp)"',
130
- `${init.body === undefined ? "" : 'printf %s "$HYB_STDIN" | base64 -d | '}curl "$@" > "$t"`,
131
- "code=$?",
132
- 'base64 -w0 "$t"',
133
- 'rm -f "$t"',
134
- 'exit "$code"',
135
- ].join("\n");
136
- const env = init.body === undefined ? {} : { HYB_STDIN: Buffer.from(init.body).toString("base64") };
137
- const result = await docker.exec(container, { cmd: ["sh", "-c", script, "sh", ...args], user, env });
138
- if (result.exitCode !== 0) {
139
- throw new Error(`fetch ${url} in ${container} failed (curl exit ${result.exitCode}): ${result.stderr.trim()}`);
140
- }
141
- return parseResponse(Buffer.from(result.stdout, "base64"));
142
- }
143
- return {
144
- readFile,
145
- readFileRange,
146
- writeFile,
147
- writeFileExclusive,
148
- removeFile,
149
- createDirectory,
150
- removeDirectory,
151
- listDirectory,
152
- stat,
153
- homeDirectory,
154
- runCommand,
155
- fetch: fetchInContainer,
156
- // A container's sockets are reached from its host (agents mount theirs there), not through exec.
157
- connect: async (path) => {
158
- throw new Error(`connecting to ${path} inside ${container} is not supported; connect through the host sandbox`);
159
- },
160
- };
161
- }
162
- /** Parses curl -i output: header blocks (interim 1xx ones skipped), then the body. */
163
- function parseResponse(raw) {
164
- let offset = 0;
165
- for (;;) {
166
- const end = raw.indexOf("\r\n\r\n", offset);
167
- if (end < 0)
168
- throw new Error("malformed HTTP response from curl");
169
- const [statusLine = "", ...headerLines] = raw.subarray(offset, end).toString("latin1").split("\r\n");
170
- offset = end + 4;
171
- const status = Number(statusLine.split(" ")[1]);
172
- if (status >= 100 && status < 200)
173
- continue;
174
- const headers = new Headers();
175
- for (const line of headerLines) {
176
- const colon = line.indexOf(":");
177
- if (colon > 0)
178
- headers.append(line.slice(0, colon).trim(), line.slice(colon + 1).trim());
179
- }
180
- // A null-body status can't be given a body, even an empty one.
181
- const body = status === 204 || status === 304 ? null : new Uint8Array(raw.subarray(offset));
182
- return new Response(body, { status, headers });
183
- }
184
- }
@@ -1,90 +0,0 @@
1
- import { type SandboxAccessibleEndpoint, type SandboxCommands, type SandboxEndpoint, type SandboxEndpointCredentials, type SandboxEndpointRequest } from "./api.ts";
2
- /** Declares the accessible-endpoint capability when a sandbox is created (local and SSH). */
3
- export type SandboxEndpointsOptions = {
4
- /**
5
- * How clients reach the machine over the network ("host" or "host:port"); often not the address
6
- * the sandbox itself uses. With a port, every endpoint listens on it, one proxy serving them all;
7
- * without, each gets a free port of its own (its auth may limit which).
8
- */
9
- accessibleHost: string;
10
- /**
11
- * The machine's firewall is ufw and drops ports by default: a proxy's port is opened with
12
- * `sudo -n ufw allow` (so the user needs passwordless sudo) and closed again with it.
13
- */
14
- needsFirewall?: boolean;
15
- /** Defaults to proxyCredentialsAuth(); exeDevProxyAuth() for an exe.dev VM. */
16
- auth?: SandboxEndpointAuth;
17
- /**
18
- * Sandbox path the proxies keep their endpoints in (their secrets: files only the user can read);
19
- * defaults to ~/.local/state/hyperbrane/endpoints.
20
- */
21
- stateDirectory?: string;
22
- };
23
- /** An accessible host, split: `port` is null when endpoints pick their own. */
24
- export type AccessibleHost = {
25
- host: string;
26
- port: number | null;
27
- };
28
- /** "host", "host:port", "[v6]:port" or a bare IPv6 address; throws SandboxEndpointError for a bad port. */
29
- export declare function parseAccessibleHost(value: string): AccessibleHost;
30
- /**
31
- * How a sandbox's endpoints are authenticated. Bound once, when the sandbox is created, to the host
32
- * clients reach it at (throws if it can't serve that host or port); then plans each endpoint.
33
- */
34
- export type SandboxEndpointAuth = {
35
- bind(accessible: AccessibleHost): BoundEndpointAuth;
36
- };
37
- export type BoundEndpointAuth = {
38
- /** Whether the backend may open the proxy's port in the host firewall: not when only a proxy in front of it may reach it. */
39
- firewall: boolean;
40
- /** Throws SandboxEndpointError for a request this auth doesn't allow. */
41
- plan(request: SandboxEndpointRequest): EndpointPlan;
42
- };
43
- export type EndpointPlan = {
44
- /** What the proxy checks on every request; null when something in front of it checks instead. */
45
- check: {
46
- scheme: "bearer" | "basic";
47
- credentials: string;
48
- } | null;
49
- /** Ports to listen on, inclusive; [0, 0] for any free one. */
50
- ports: [number, number];
51
- /** Ports in that range it must never listen on. */
52
- except?: number[];
53
- /**
54
- * Where clients reach the proxy on the port the endpoint ends up on (its base URL; the endpoint is
55
- * a path under it), and what they present: made public there first if that takes doing (Boat).
56
- */
57
- publish(port: number): Promise<{
58
- url: string;
59
- credentials: SandboxEndpointCredentials;
60
- }>;
61
- };
62
- /**
63
- * The default: the proxy itself checks credentials it mints per endpoint (bearer or basic). The
64
- * proxy speaks plain HTTP; the accessible host is assumed to terminate TLS in front of it, on the
65
- * same port, so clients are given https URLs.
66
- */
67
- export declare function proxyCredentialsAuth(): SandboxEndpointAuth;
68
- /** The port a request must listen on: the accessible host's, or its own; null for any. */
69
- export declare function pinnedPort(pinned: number | null, requested: number | undefined): number | null;
70
- export declare function checkPort(port: number): void;
71
- /** The port an endpoint listens on and its name there, from its id; throws for anything that isn't an endpoint id. */
72
- export declare function parseEndpointId(endpointId: string): {
73
- port: number;
74
- name: string;
75
- };
76
- /**
77
- * The capability for a sandbox whose commands run on the machine (`where`, for errors). Endpoints
78
- * are records there, served by proxies started there, detached, one per port: a port of the
79
- * accessible host's is shared by all, otherwise each endpoint starts one on a port of its own. A
80
- * proxy that stopped is started again by ensureEndpoint, serving the same records. With a firewall, a
81
- * proxy's port is opened once it listens (it is stopped again if that fails) and closed when it stops.
82
- */
83
- export type ProxyEndpoints = SandboxAccessibleEndpoint & {
84
- /**
85
- * exposeEndpoint under a name of the caller's (a UUID), replacing what that name held: the same
86
- * URL every time, for endpoints the sandbox keeps for itself (boatService.ts).
87
- */
88
- exposeEndpointAs(name: string, request: SandboxEndpointRequest): Promise<SandboxEndpoint>;
89
- };
90
- export declare function createProxyEndpoints(commands: SandboxCommands, options: SandboxEndpointsOptions, where: string): ProxyEndpoints;