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,50 +1,8 @@
1
- import { type DockerClient } from "../lib/docker.ts";
2
- import type { ManagedImage } from "../lib/image.ts";
3
1
  import type { Sandbox } from "../sandbox/api.ts";
4
- import { type Portal } from "./api.ts";
5
- import type { GatewayStorage } from "./gatewayStorage.ts";
6
- import type { PortalStorage } from "./portalStorage.ts";
2
+ import type { PortalClient } from "./api.ts";
7
3
  /** @sealed */
8
- export interface PortalService {
9
- createPortal(portal: Portal): Promise<Portal>;
10
- destroyPortal(portalName: string): Promise<void>;
11
- listPortals(): Promise<Portal[]>;
12
- /**
13
- * Re-wires the ready portals (optionally only those of one gateway or dimension) whose containers
14
- * are running and whose link is gone: a container restart takes its network namespace, and the
15
- * portal's veth, with it. Portals still wired cost one look at each gateway's interfaces, so this
16
- * is cheap enough to run before every command. Failures are logged per portal rather than thrown.
17
- */
18
- restorePortals(filter?: {
19
- gatewayName?: string;
20
- dimensionName?: string;
21
- }): Promise<void>;
4
+ export interface PortalService extends PortalClient {
22
5
  }
23
- type PortalLink = {
24
- dimensionIf: string;
25
- gatewayIf: string;
26
- dimensionIp: string;
27
- gatewayIp: string;
28
- metric: number;
29
- };
30
- /**
31
- * Interface names come from the portal name (so a portal can always be found again) and addresses
32
- * from its claimed subnet. The gateway only intercepts interfaces named "hvg*".
33
- */
34
- export declare function portalLink(portalName: string, subnet: number): PortalLink;
35
- /** The OS trust bundle; the gateways' CAs end up in it. */
36
- export declare const OS_CA_BUNDLE = "/etc/ssl/certs/ca-certificates.crt";
37
- /**
38
- * `host` is the machine the docker daemon runs on, rooted at "/" (see createGatewayService).
39
- * `image` is the gateway image: it carries the wiring script run by the privileged helper.
40
- */
41
- export declare function createPortalService(storage: PortalStorage, gateways: GatewayStorage, docker: DockerClient, image: ManagedImage, host: Sandbox): PortalService;
42
- /** @sealed */
43
- export interface PortalWatcher {
44
- /** Restores every portal, then keeps restoring those of each hyperbrane container that starts. */
45
- start(): void;
46
- stop(): Promise<void>;
47
- }
48
- /** Run one per host: the portals of a container that restarts are gone until something re-wires them. */
49
- export declare function createPortalWatcher(portals: PortalService, docker: DockerClient): PortalWatcher;
50
- export {};
6
+ export declare function createPortalService(host: Sandbox, { dataDirectory }?: {
7
+ dataDirectory?: string;
8
+ }): PortalService;
@@ -1,359 +1,29 @@
1
- import { createHash } from "node:crypto";
2
- import { containerName as dimensionContainerName } from "../dimension/service.js";
3
- import { DockerError } from "../lib/docker.js";
4
- import { sleep } from "../lib/sleep.js";
5
- import { GatewayConflictError, GatewayNotFoundError } from "./api.js";
6
- import { assertValidName, gatewayCaCertificatePath, gatewayContainerName } from "./gatewayService.js";
7
- const RESTORE_RECHECK_MS = 1_000;
8
- /** Each portal is a point-to-point /30 out of 10.77.0.0/16. */
9
- const SUBNET_COUNT = 1 << 14;
10
- /** A dimension's default routes are ordered by subnet: its portal with the lowest one is preferred. */
11
- const ROUTE_METRIC_BASE = 100;
12
- /**
13
- * Interface names come from the portal name (so a portal can always be found again) and addresses
14
- * from its claimed subnet. The gateway only intercepts interfaces named "hvg*".
15
- */
16
- export function portalLink(portalName, subnet) {
17
- const hash = createHash("sha256").update(portalName).digest("hex").slice(0, 8);
18
- const base = `10.77.${subnet >> 6}`;
19
- const offset = (subnet & 0x3f) << 2;
20
- return {
21
- dimensionIf: `hvd${hash}`,
22
- gatewayIf: `hvg${hash}`,
23
- gatewayIp: `${base}.${offset + 1}`,
24
- dimensionIp: `${base}.${offset + 2}`,
25
- metric: ROUTE_METRIC_BASE + subnet,
26
- };
27
- }
28
- /** The OS trust bundle; the gateways' CAs end up in it. */
29
- export const OS_CA_BUNDLE = "/etc/ssl/certs/ca-certificates.crt";
30
- // Run as root in the dimension: installs GATEWAY_CA as the gateway's certificate in the OS trust
31
- // store, or removes it when empty. Nothing but its gateways' CAs ever signs what a dimension sees
32
- // (every TCP connection is intercepted), so without the ca-certificates package the bundle can be
33
- // just those CAs. Node ignores the OS store, so while any gateway is trusted NODE_EXTRA_CA_CERTS
34
- // points it at the bundle, for login shells (profile.d) and PAM sessions (/etc/environment); a
35
- // running container's own environment can't be changed. DNS needs no setup: the gateway answers
36
- // UDP 53 whatever address it was sent to.
37
- const TRUST_GATEWAY_SCRIPT = `set -eu
38
- # Portals of one dimension are wired concurrently (e.g. restored together after a restart), and
39
- # they share the trust store and /etc/environment.
40
- exec 9>/run/hyperbrane-trust.lock
41
- flock 9
42
- bundle=${OS_CA_BUNDLE}
43
- mkdir -p /usr/local/share/ca-certificates /etc/ssl/certs /etc/profile.d
44
- cert="/usr/local/share/ca-certificates/hyperbrane-gateway-$GATEWAY.crt"
45
- if [ -n "$GATEWAY_CA" ]; then printf '%s\\n' "$GATEWAY_CA" > "$cert"; else rm -f "$cert"; fi
46
- if command -v update-ca-certificates >/dev/null; then
47
- update-ca-certificates --fresh >/dev/null
48
- else
49
- cat /usr/local/share/ca-certificates/hyperbrane-gateway-*.crt > "$bundle" 2>/dev/null || : > "$bundle"
50
- if command -v openssl >/dev/null; then openssl rehash /etc/ssl/certs; fi
51
- fi
52
- { grep -v '^NODE_EXTRA_CA_CERTS=' /etc/environment 2>/dev/null || true; } > /etc/environment.hyperbrane
53
- if ls /usr/local/share/ca-certificates/hyperbrane-gateway-*.crt >/dev/null 2>&1; then
54
- echo "NODE_EXTRA_CA_CERTS=$bundle" >> /etc/environment.hyperbrane
55
- echo "export NODE_EXTRA_CA_CERTS=$bundle" > /etc/profile.d/hyperbrane-gateway.sh
56
- else
57
- rm -f /etc/profile.d/hyperbrane-gateway.sh
58
- fi
59
- mv /etc/environment.hyperbrane /etc/environment
60
- `;
61
- /**
62
- * `host` is the machine the docker daemon runs on, rooted at "/" (see createGatewayService).
63
- * `image` is the gateway image: it carries the wiring script run by the privileged helper.
64
- */
65
- export function createPortalService(storage, gateways, docker, image, host) {
66
- // Re-running re-wires, so it also repairs a portal whose veth went away.
1
+ import { createDockerClient } from "../lib/docker.js";
2
+ import { gatewayControl } from "./control.js";
3
+ import { assertValidName } from "./gatewayService.js";
4
+ import { createGatewayImage } from "./image.js";
5
+ export function createPortalService(host, { dataDirectory } = {}) {
6
+ const control = gatewayControl(host, () => createGatewayImage(createDockerClient(host)).ensure(), { dataDirectory });
67
7
  async function createPortal(portal) {
68
8
  assertValidName("portal", portal.name);
69
9
  assertValidName("dimension", portal.dimensionName);
70
10
  assertValidName("gateway", portal.gatewayName);
71
- const gateway = await gateways.readGateway(portal.gatewayName);
72
- if (gateway?.state !== "ready")
73
- throw new GatewayNotFoundError(`gateway ${portal.gatewayName} is not ready`);
74
- // Checked again when wiring; failing here first leaves no record behind.
75
- await runningPid(gatewayContainerName(portal.gatewayName), "hyperbrane.gateway", portal.gatewayName);
76
- await runningPid(dimensionContainerName(portal.dimensionName), "hyperbrane.dimension", portal.dimensionName);
77
- let record = await storage.insertPortal({ ...portal, state: "creating" });
78
- if (record.state === "destroying")
79
- throw new GatewayConflictError(`portal ${portal.name} is being destroyed`);
80
- if (record.dimensionName !== portal.dimensionName || record.gatewayName !== portal.gatewayName) {
81
- throw new GatewayConflictError(`portal ${portal.name} already exists with a different spec`);
82
- }
83
- if (record.subnet === undefined) {
84
- const subnet = await storage.claimSubnet(portal.name);
85
- if (subnet >= SUBNET_COUNT) {
86
- await storage.releaseSubnet(subnet);
87
- throw new GatewayConflictError("no portal subnets left");
88
- }
89
- record = { ...record, subnet };
90
- await storage.savePortal(record);
91
- }
92
- await connect(record);
93
- if (record.state !== "ready")
94
- await storage.savePortal({ ...record, state: "ready" });
95
- return publicPortal(record);
96
- }
97
- async function connect(record, pids) {
98
- const { gateway, dimension } = pids ?? (await runningPids(record));
99
- await wire(record.name, portalLink(record.name, record.subnet), dimension, gateway);
100
- await trustGateway(dimensionContainerName(record.dimensionName), record.gatewayName, true);
101
- }
102
- async function runningPids(record) {
103
- return {
104
- gateway: await runningPid(gatewayContainerName(record.gatewayName), "hyperbrane.gateway", record.gatewayName),
105
- dimension: await runningPid(dimensionContainerName(record.dimensionName), "hyperbrane.dimension", record.dimensionName),
106
- };
107
- }
108
- async function runningPid(name, label, owner) {
109
- const kind = label.replace("hyperbrane.", "");
110
- const container = await docker.inspectContainer(name);
111
- if (!container || container.Config?.Labels?.[label] !== owner) {
112
- throw new GatewayNotFoundError(`${kind} ${owner} does not exist`);
113
- }
114
- if (!container.State?.Running || !container.State.Pid) {
115
- throw new GatewayConflictError(`${kind} ${owner} is not running`);
116
- }
117
- return container.State.Pid;
118
- }
119
- // Moving a veth between two containers' namespaces takes the host's view of both, so it runs in a
120
- // throwaway privileged container; neither the dimension nor the gateway gets that power.
121
- async function wire(portalName, link, dimensionPid, gatewayPid) {
122
- const helper = `hybwire-${crypto.randomUUID().slice(0, 8)}`;
123
- await docker.createContainer(helper, {
124
- Image: await image.ensure(),
125
- Cmd: ["sleep", "infinity"],
126
- Labels: { hyperbrane: "true", "hyperbrane.helper": "wire-portal" },
127
- HostConfig: {
128
- NetworkMode: "host",
129
- PidMode: "host",
130
- Privileged: true,
131
- Binds: ["/run:/hostrun"],
132
- Init: true,
133
- },
134
- });
135
- try {
136
- await docker.startContainer(helper);
137
- const result = await docker.exec(helper, {
138
- cmd: ["hyb-wire-portal"],
139
- user: "root",
140
- env: {
141
- PORTAL: portalName,
142
- DIMENSION_PID: String(dimensionPid),
143
- GATEWAY_PID: String(gatewayPid),
144
- DIMENSION_IF: link.dimensionIf,
145
- GATEWAY_IF: link.gatewayIf,
146
- DIMENSION_IP: link.dimensionIp,
147
- GATEWAY_IP: link.gatewayIp,
148
- ROUTE_METRIC: String(link.metric),
149
- },
150
- });
151
- if (result.exitCode !== 0)
152
- throw new Error(`wiring portal ${portalName} failed: ${result.stderr.trim()}`);
153
- }
154
- finally {
155
- await docker.removeContainer(helper);
156
- }
11
+ await control.createPortal(portal);
12
+ return { name: portal.name, dimensionName: portal.dimensionName, gatewayName: portal.gatewayName };
157
13
  }
158
- async function trustGateway(dimensionName, gatewayName, trusted) {
159
- let ca = "";
160
- if (trusted) {
161
- const stateDirectory = await gateways.ensureStateDirectory(gatewayName);
162
- ca = new TextDecoder().decode(await host.readFile(gatewayCaCertificatePath(stateDirectory))).trim();
163
- }
164
- const result = await docker.exec(dimensionName, {
165
- cmd: ["sh", "-c", TRUST_GATEWAY_SCRIPT],
166
- user: "root",
167
- env: { GATEWAY: gatewayName, GATEWAY_CA: ca },
168
- });
169
- if (result.exitCode !== 0) {
170
- throw new Error(`updating gateway ${gatewayName} trust in ${dimensionName} failed (exit ${result.exitCode}): ${result.stderr.trim()}`);
171
- }
172
- }
173
- // Deleting the gateway end removes the pair, and the dimension's route through it.
174
- // Marked first: a restore already under way re-checks the record after wiring and undoes its work,
175
- // and the link is removed after the mark, so whichever runs last, the link is gone.
176
14
  async function destroyPortal(portalName) {
177
15
  assertValidName("portal", portalName);
178
- const record = await storage.readPortal(portalName);
179
- if (!record)
180
- return;
181
- if (record.state !== "destroying")
182
- await storage.savePortal({ ...record, state: "destroying" });
183
- // A restore under way finishes first (seeing the mark, it undoes what it wired), so nothing it
184
- // does lands after this returns; one that starts from now on sees the mark and does nothing.
185
- while (restoring.has(portalName))
186
- await restoring.get(portalName).catch(() => { });
187
- if (record.subnet !== undefined) {
188
- await ignoringStopped(() => unwire(record.gatewayName, portalLink(portalName, record.subnet)));
189
- await storage.releaseSubnet(record.subnet);
190
- }
191
- await untrustUnlessShared(record);
192
- await storage.removePortal(portalName);
193
- }
194
- /** Takes the gateway's CA out of the dimension, unless another of its portals goes to that gateway. */
195
- async function untrustUnlessShared(record) {
196
- const sameGateway = (await storage.listPortals()).some((other) => other.name !== record.name &&
197
- other.dimensionName === record.dimensionName &&
198
- other.gatewayName === record.gatewayName);
199
- if (!sameGateway) {
200
- await ignoringStopped(() => trustGateway(dimensionContainerName(record.dimensionName), record.gatewayName, false));
201
- }
202
- }
203
- async function unwire(gatewayName, link) {
204
- const result = await docker.exec(gatewayContainerName(gatewayName), {
205
- cmd: ["sh", "-c", `ip link del ${link.gatewayIf} 2>/dev/null || true`],
206
- user: "root",
207
- });
208
- if (result.exitCode !== 0)
209
- throw new Error(`removing ${link.gatewayIf} failed: ${result.stderr.trim()}`);
210
- }
211
- async function listPortals() {
212
- return (await storage.listPortals()).map(publicPortal);
213
- }
214
- async function restorePortals({ gatewayName, dimensionName } = {}) {
215
- const records = (await storage.listPortals()).filter((record) => record.state === "ready" &&
216
- (gatewayName === undefined || record.gatewayName === gatewayName) &&
217
- (dimensionName === undefined || record.dimensionName === dimensionName));
218
- // A veth pair only ever goes as a whole, so a gateway end still there means the portal is intact.
219
- const interfaces = new Map();
220
- const gatewayInterfaces = (name) => {
221
- if (!interfaces.has(name))
222
- interfaces.set(name, listInterfaces(gatewayContainerName(name)));
223
- return interfaces.get(name);
224
- };
225
- await Promise.all(records.map(async (record) => {
226
- if ((await gatewayInterfaces(record.gatewayName))?.has(portalLink(record.name, record.subnet).gatewayIf))
227
- return;
228
- await restoreOnce(record);
229
- }));
230
- }
231
- // One restore of a portal at a time, however many ask (a restart's own and the watcher's): two
232
- // wiring the same link at once fail each other, and the retry could land after a destroy. Each
233
- // waits for the one before rather than joining it: that one may have given up on a container that
234
- // has since restarted, so once it's done the link is looked at again.
235
- const restoring = new Map();
236
- async function restoreOnce(record) {
237
- while (restoring.has(record.name)) {
238
- await restoring.get(record.name).catch(() => { });
239
- const link = portalLink(record.name, record.subnet);
240
- if ((await listInterfaces(gatewayContainerName(record.gatewayName)))?.has(link.gatewayIf))
241
- return;
242
- }
243
- const restored = restore(record).finally(() => restoring.delete(record.name));
244
- restoring.set(record.name, restored);
245
- return restored;
16
+ await control.destroyPortal(portalName);
246
17
  }
247
- async function restore(listed) {
248
- // As it is now: one being destroyed is left alone.
249
- const record = await storage.readPortal(listed.name);
250
- if (record?.state !== "ready")
251
- return;
252
- let pids;
253
- try {
254
- pids = await runningPids(record);
255
- }
256
- catch (error) {
257
- // A stopped or removed container gets wired when it starts again, or never.
258
- if (error instanceof GatewayConflictError || error instanceof GatewayNotFoundError)
259
- return;
260
- throw error;
261
- }
262
- for (let attempt = 1;; attempt++) {
263
- try {
264
- await connect(record, pids);
265
- // Destroyed meanwhile: neither the link nor the trust just made may outlive it (see destroyPortal).
266
- if ((await storage.readPortal(record.name))?.state !== "ready") {
267
- await ignoringStopped(() => unwire(record.gatewayName, portalLink(record.name, record.subnet)));
268
- await untrustUnlessShared(record);
269
- }
270
- return;
271
- }
272
- catch (error) {
273
- // A container that stopped or restarted meanwhile (killing what ran in it) is restored by
274
- // its own start event. Docker kills a restarting container's processes before its state
275
- // changes, so give it a moment before deciding it's still the same container.
276
- await sleep(RESTORE_RECHECK_MS);
277
- const now = await runningPids(record).catch(() => null);
278
- if (now?.gateway !== pids.gateway || now?.dimension !== pids.dimension)
279
- return;
280
- if (attempt === 2)
281
- return console.warn(`restoring portal ${record.name} failed:`, error);
282
- }
283
- }
18
+ async function restorePortals(filter = {}) {
19
+ const failed = await control.restorePortals(filter);
20
+ if (failed)
21
+ console.warn(failed);
284
22
  }
285
- /** Null when the container isn't running. */
286
- async function listInterfaces(container) {
287
- try {
288
- const result = await docker.exec(container, { cmd: ["ip", "-br", "link"] });
289
- if (result.exitCode !== 0)
290
- return null;
291
- return new Set(result.stdout.split("\n").map((line) => line.split(/[@\s]/)[0]).filter(Boolean));
292
- }
293
- catch (error) {
294
- if (error instanceof DockerError && (error.status === 404 || error.status === 409))
295
- return null;
296
- throw error;
297
- }
298
- }
299
- return { createPortal, destroyPortal, listPortals, restorePortals };
300
- }
301
- /** Runs `action` unless its container isn't running, in which case there's nothing to undo. */
302
- async function ignoringStopped(action) {
303
- try {
304
- await action();
305
- }
306
- catch (error) {
307
- if (!(error instanceof DockerError && (error.status === 404 || error.status === 409)))
308
- throw error;
309
- }
310
- }
311
- function publicPortal({ name, dimensionName, gatewayName }) {
312
- return { name, dimensionName, gatewayName };
313
- }
314
- const WATCH_RETRY_MS = 1_000;
315
- /** Run one per host: the portals of a container that restarts are gone until something re-wires them. */
316
- export function createPortalWatcher(portals, docker) {
317
- let controller;
318
- let watching;
319
- function restore(filter) {
320
- portals.restorePortals(filter).catch((error) => console.warn("restoring portals failed:", error));
321
- }
322
- async function watch(signal) {
323
- while (!signal.aborted) {
324
- try {
325
- const events = docker
326
- .events({ type: ["container"], event: ["start"], label: ["hyperbrane"] }, signal)[Symbol.asyncIterator]();
327
- // Subscribe first, then restore: a container starting in between is covered either way.
328
- let next = events.next();
329
- restore();
330
- for (let event = await next; !event.done; event = await next) {
331
- next = events.next();
332
- const labels = event.value.Actor?.Attributes ?? {};
333
- if (labels["hyperbrane.gateway"])
334
- restore({ gatewayName: labels["hyperbrane.gateway"] });
335
- if (labels["hyperbrane.dimension"])
336
- restore({ dimensionName: labels["hyperbrane.dimension"] });
337
- }
338
- }
339
- catch (error) {
340
- console.warn("watching docker events failed:", error);
341
- }
342
- if (!signal.aborted)
343
- await sleep(WATCH_RETRY_MS);
344
- }
345
- }
346
- function start() {
347
- if (controller)
348
- return;
349
- controller = new AbortController();
350
- watching = watch(controller.signal);
351
- }
352
- async function stop() {
353
- controller?.abort();
354
- await watching;
355
- controller = undefined;
356
- watching = undefined;
357
- }
358
- return { start, stop };
23
+ return {
24
+ createPortal: (portal) => createPortal(portal),
25
+ destroyPortal: (portalName) => destroyPortal(portalName),
26
+ listPortals: () => control.listPortals(),
27
+ restorePortals,
28
+ };
359
29
  }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,205 @@
1
+ import { randomBytes, randomUUID } from "node:crypto";
2
+ import { readdir, readFile, writeFile } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+ import { jwtVerify } from "jose";
5
+ import { dimensionLayout, isDimensionName } from "../dimension/api.js";
6
+ import { createLocalService } from "../sandbox/providers/local/localService.js";
7
+ const TRANSPORT_PROTOCOL = 1;
8
+ const TOKEN_PARAM = "hyb_token";
9
+ const HEARTBEAT_MS = 15_000;
10
+ const connections = new Map();
11
+ async function wired(dimensionName) {
12
+ const files = await readdir(join(data, "portals")).catch(() => []);
13
+ for (const file of files.filter((name) => name.endsWith(".json"))) {
14
+ const portal = await readFile(join(data, "portals", file), "utf8").then((text) => JSON.parse(text), () => null);
15
+ if (portal?.gatewayName === gateway && portal.dimensionName === dimensionName && portal.state === "ready")
16
+ return true;
17
+ }
18
+ return false;
19
+ }
20
+ async function authorized(url) {
21
+ const token = url.searchParams.get(TOKEN_PARAM);
22
+ if (!token)
23
+ return false;
24
+ try {
25
+ await jwtVerify(token, key, { algorithms: ["HS256"], requiredClaims: ["exp"] });
26
+ return true;
27
+ }
28
+ catch {
29
+ return false;
30
+ }
31
+ }
32
+ function cors(request, response) {
33
+ const origin = request.headers.get("origin");
34
+ if (!origin)
35
+ return response;
36
+ response.headers.set("access-control-allow-origin", origin);
37
+ response.headers.append("vary", "Origin");
38
+ return response;
39
+ }
40
+ async function preflight(request) {
41
+ const headers = new Headers({
42
+ "access-control-allow-methods": "GET, POST, OPTIONS",
43
+ "access-control-max-age": "600",
44
+ });
45
+ const asked = request.headers.get("access-control-request-headers");
46
+ if (asked)
47
+ headers.set("access-control-allow-headers", asked);
48
+ return cors(request, new Response(null, { status: 204, headers }));
49
+ }
50
+ async function answer(handler, request, server) {
51
+ server.timeout(request, 0);
52
+ if (!(await authorized(new URL(request.url))))
53
+ return Response.json({ error: "a valid token is required" }, { status: 401 });
54
+ try {
55
+ return await handler(request, server);
56
+ }
57
+ catch (error) {
58
+ const { name, message } = error instanceof Error ? error : new Error(String(error));
59
+ return Response.json({ name, message }, { status: 500 });
60
+ }
61
+ }
62
+ function guarded(handler) {
63
+ return async (request, server) => {
64
+ const response = await answer(handler, request, server);
65
+ return response && cors(request, response);
66
+ };
67
+ }
68
+ function relay(connection, send, end) {
69
+ connection.onData(send);
70
+ void connection.closed.then(end);
71
+ }
72
+ const encoder = new TextEncoder();
73
+ function multipartFrames() {
74
+ const boundary = randomBytes(24).toString("hex");
75
+ const part = (type, data) => Buffer.concat([encoder.encode(`\r\ncontent-type: ${type}\r\n\r\n`), data, encoder.encode(`\r\n--${boundary}`)]);
76
+ return {
77
+ contentType: `multipart/mixed; boundary="${boundary}"`,
78
+ open: (id) => Buffer.concat([encoder.encode(`--${boundary}`), part("application/json", encoder.encode(JSON.stringify({ connection: id })))]),
79
+ data: (chunk) => part("application/octet-stream", chunk),
80
+ heartbeat: part("application/octet-stream", new Uint8Array()),
81
+ end: encoder.encode("--\r\n"),
82
+ };
83
+ }
84
+ function lineFrames() {
85
+ const decoder = new TextDecoder();
86
+ let pending = "";
87
+ return {
88
+ contentType: "text/event-stream",
89
+ open: (id) => encoder.encode(`event: open\ndata: ${JSON.stringify({ connection: id })}\n\n`),
90
+ data(chunk) {
91
+ const lines = (pending + decoder.decode(chunk, { stream: true })).split("\n");
92
+ pending = lines.pop();
93
+ return encoder.encode(lines.map((line) => `data: ${line.replace(/\r$/, "")}\n\n`).join(""));
94
+ },
95
+ heartbeat: encoder.encode(": ping\n\n"),
96
+ end: encoder.encode("event: close\ndata: {}\n\n"),
97
+ };
98
+ }
99
+ function stream(connection, frames) {
100
+ const id = randomUUID();
101
+ let heartbeat;
102
+ let open = true;
103
+ const finish = () => {
104
+ open = false;
105
+ clearInterval(heartbeat);
106
+ connections.delete(id);
107
+ connection.close();
108
+ };
109
+ connections.set(id, connection);
110
+ const body = new ReadableStream({
111
+ start(controller) {
112
+ const send = (bytes) => {
113
+ if (open && bytes.length > 0)
114
+ controller.enqueue(bytes);
115
+ };
116
+ send(frames.open(id));
117
+ heartbeat = setInterval(() => send(frames.heartbeat), HEARTBEAT_MS);
118
+ relay(connection, (data) => send(frames.data(data)), () => {
119
+ if (!open)
120
+ return;
121
+ send(frames.end);
122
+ finish();
123
+ controller.close();
124
+ });
125
+ },
126
+ cancel: finish,
127
+ });
128
+ return new Response(body, { headers: { "content-type": frames.contentType, "cache-control": "no-cache, no-transform", "x-accel-buffering": "no" } });
129
+ }
130
+ async function write(request) {
131
+ const connection = connections.get(request.params.id);
132
+ if (!connection)
133
+ return Response.json({ error: "no such connection" }, { status: 404 });
134
+ connection.write(new Uint8Array(await request.arrayBuffer()));
135
+ return new Response(null, { status: 204 });
136
+ }
137
+ async function connect(request, server) {
138
+ const params = new URL(request.url).searchParams;
139
+ const dimensionName = params.get("dimension") ?? "";
140
+ const agentName = params.get("agent");
141
+ if (!isDimensionName(dimensionName) || (agentName !== null && !isDimensionName(agentName))) {
142
+ return Response.json({ error: "dimension and agent are names" }, { status: 400 });
143
+ }
144
+ if (!(await wired(dimensionName)))
145
+ return Response.json({ error: `dimension ${dimensionName} has no portal to gateway ${gateway}` }, { status: 403 });
146
+ const socket = agentName === null ? dimensionLayout.transportSocketPath(data, dimensionName) : join(dimensionLayout.runDirectory(data, dimensionName), `${agentName}.sock`);
147
+ const connection = await machine.connect(socket).catch((error) => error);
148
+ if (connection instanceof Error)
149
+ return Response.json({ error: connection.message }, { status: 502 });
150
+ if (request.headers.get("upgrade")?.toLowerCase() !== "websocket")
151
+ return stream(connection, agentName === null ? multipartFrames() : lineFrames());
152
+ if (server.upgrade(request, { data: connection }))
153
+ return undefined;
154
+ connection.close();
155
+ return Response.json({ error: "the websocket upgrade failed" }, { status: 400 });
156
+ }
157
+ function serve(port) {
158
+ return Bun.serve({
159
+ port,
160
+ hostname: "0.0.0.0",
161
+ routes: {
162
+ "/connect": {
163
+ GET: guarded(connect),
164
+ OPTIONS: preflight,
165
+ },
166
+ "/connect/:id": {
167
+ POST: guarded(write),
168
+ OPTIONS: preflight,
169
+ },
170
+ },
171
+ websocket: {
172
+ open(socket) {
173
+ relay(socket.data, (data) => socket.send(data), () => socket.close());
174
+ },
175
+ message(socket, message) {
176
+ socket.data.write(typeof message === "string" ? Buffer.from(message) : message);
177
+ },
178
+ close(socket) {
179
+ socket.data.close();
180
+ },
181
+ },
182
+ });
183
+ }
184
+ function required(name) {
185
+ const value = process.env[name];
186
+ if (!value)
187
+ throw new Error(`${name} is not set`);
188
+ return value;
189
+ }
190
+ process.umask(0o077);
191
+ const dir = required("HYB_TRANSPORT_DIR");
192
+ const build = required("HYB_TRANSPORT_BUILD");
193
+ const port = Number(required("HYB_TRANSPORT_PORT"));
194
+ const gateway = required("HYB_GATEWAY");
195
+ const data = required("HYB_DATA");
196
+ const key = new TextEncoder().encode(await readFile(join(dir, "secret"), "utf8"));
197
+ const machine = createLocalService({ kind: "local" });
198
+ const server = serve(port);
199
+ for (const signal of ["SIGTERM", "SIGINT"]) {
200
+ process.once(signal, () => {
201
+ server.stop(true);
202
+ process.exit(0);
203
+ });
204
+ }
205
+ await writeFile(join(dir, "ready"), `${process.pid} ${build} ${TRANSPORT_PROTOCOL}\n`);
@@ -0,0 +1,2 @@
1
+ export declare const transportEntry: () => string;
2
+ export declare const transportBundle: () => Promise<import("../lib/scriptBundle.ts").ScriptBundle>;
@@ -0,0 +1 @@
1
+ export declare const TRANSPORT_SOURCE: string;