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.
- package/README.md +67 -25
- package/dist/agent/agentClient.d.ts +5 -0
- package/dist/agent/agentClient.js +4 -0
- package/dist/agent/agentService.d.ts +9 -37
- package/dist/agent/agentService.js +62 -322
- package/dist/agent/agentStorage.d.ts +1 -20
- package/dist/agent/agentStorage.js +0 -30
- package/dist/agent/api.d.ts +80 -151
- package/dist/agent/api.js +1 -8
- package/dist/agent/client.d.ts +3 -1
- package/dist/agent/client.js +2 -3
- package/dist/agent/control.d.ts +23 -0
- package/dist/agent/control.js +479 -0
- package/dist/agent/hostTranscript.d.ts +0 -6
- package/dist/agent/hostTranscript.js +3 -14
- package/dist/agent/session/backend.d.ts +17 -32
- package/dist/agent/session/claude/home.d.ts +0 -1
- package/dist/agent/session/claude/home.js +0 -1
- package/dist/agent/session/claude/provision.d.ts +10 -0
- package/dist/agent/session/claude/provision.js +17 -0
- package/dist/agent/session/claude/transcript.d.ts +2 -7
- package/dist/agent/session/claude/transcript.js +68 -14
- package/dist/agent/session/client.d.ts +1 -10
- package/dist/agent/session/client.js +0 -3
- package/dist/agent/session/codex/provision.d.ts +14 -0
- package/dist/agent/session/codex/provision.js +25 -0
- package/dist/agent/session/fileTranscript.d.ts +6 -49
- package/dist/agent/session/fileTranscript.js +26 -82
- package/dist/agent/session/install.d.ts +5 -0
- package/dist/agent/session/install.js +35 -0
- package/dist/agent/session/sessionStorage.d.ts +4 -35
- package/dist/agent/session/sessionStorage.js +10 -16
- package/dist/agent/session/sharedConnection.d.ts +0 -8
- package/dist/agent/session/sharedConnection.js +0 -7
- package/dist/agent/session/storedTranscripts.d.ts +1 -6
- package/dist/agent/session/storedTranscripts.js +0 -3
- package/dist/agent/session/worker/jsonRpc.d.ts +2 -13
- package/dist/agent/session/worker/jsonRpc.js +2 -11
- package/dist/agent/session/worker/protocol.d.ts +26 -20
- package/dist/agent/session/worker/protocol.js +29 -18
- package/dist/agent/session/worker/workerParams.d.ts +0 -5
- package/dist/agent/session/worker/workerParams.js +14 -14
- package/dist/agent/session/workerConnection.d.ts +2 -13
- package/dist/agent/session/workerConnection.js +7 -12
- package/dist/agent/session/workerGatewayConnection.d.ts +18 -0
- package/dist/agent/session/workerGatewayConnection.js +86 -0
- package/dist/agent/vendor-types/manifest.json +87 -0
- package/dist/agent/workerBundle.d.ts +4 -19
- package/dist/agent/workerBundle.generated.js +1 -1
- package/dist/agent/workerBundle.js +3 -30
- package/dist/agent/workerReplacement.d.ts +3 -0
- package/dist/agent/workerReplacement.js +49 -0
- package/dist/cli/agent.js +31 -16
- package/dist/cli/command.d.ts +10 -11
- package/dist/cli/command.js +0 -4
- package/dist/cli/dimension.js +6 -6
- package/dist/cli/gateway.js +3 -6
- package/dist/cli/main.js +14 -31
- package/dist/cli/streamPrinter.d.ts +2 -10
- package/dist/cli/streamPrinter.js +16 -46
- package/dist/cli/table.d.ts +0 -3
- package/dist/cli/table.js +0 -4
- package/dist/dimension/api.d.ts +39 -27
- package/dist/dimension/api.js +28 -47
- package/dist/dimension/client.d.ts +5 -0
- package/dist/dimension/client.js +4 -0
- package/dist/dimension/control.d.ts +21 -0
- package/dist/dimension/control.js +328 -0
- package/dist/dimension/image.d.ts +0 -5
- package/dist/dimension/image.js +0 -8
- package/dist/dimension/imageFiles.generated.js +1 -1
- package/dist/dimension/paths.d.ts +5 -4
- package/dist/dimension/paths.js +15 -6
- package/dist/dimension/service.d.ts +8 -24
- package/dist/dimension/service.js +60 -228
- package/dist/dimension/transport-worker.d.ts +1 -0
- package/dist/dimension/transport-worker.js +151 -0
- package/dist/dimension/transportBundle.d.ts +2 -0
- package/dist/dimension/transportBundle.generated.d.ts +1 -0
- package/dist/dimension/transportBundle.generated.js +2 -0
- package/dist/dimension/transportBundle.js +4 -0
- package/dist/dimension/transportClient.d.ts +10 -0
- package/dist/dimension/transportClient.js +115 -0
- package/dist/dimension/transportProtocol.d.ts +53 -0
- package/dist/dimension/transportProtocol.js +8 -0
- package/dist/index.d.ts +5 -9
- package/dist/index.js +5 -19
- package/dist/lib/control.d.ts +8 -0
- package/dist/lib/control.js +62 -0
- package/dist/lib/docker.d.ts +0 -22
- package/dist/lib/docker.js +0 -10
- package/dist/lib/image.d.ts +0 -3
- package/dist/lib/image.js +0 -2
- package/dist/lib/jsonDirectory.d.ts +0 -13
- package/dist/lib/jsonDirectory.js +10 -15
- package/dist/lib/scriptBundle.d.ts +6 -0
- package/dist/lib/scriptBundle.js +28 -0
- package/dist/lib/sh.d.ts +2 -0
- package/dist/lib/sh.js +8 -0
- package/dist/lib/sleep.d.ts +0 -1
- package/dist/lib/sleep.js +0 -1
- package/dist/lib/streamFetch.d.ts +15 -0
- package/dist/lib/streamFetch.js +150 -0
- package/dist/lib/tar.d.ts +0 -1
- package/dist/lib/tar.js +5 -11
- package/dist/portal-gateway/api.d.ts +13 -44
- package/dist/portal-gateway/api.js +1 -3
- package/dist/portal-gateway/control.d.ts +42 -0
- package/dist/portal-gateway/control.js +657 -0
- package/dist/portal-gateway/gatewayClient.d.ts +5 -0
- package/dist/portal-gateway/gatewayClient.js +4 -0
- package/dist/portal-gateway/gatewayService.d.ts +8 -38
- package/dist/portal-gateway/gatewayService.js +68 -254
- package/dist/portal-gateway/image.d.ts +0 -1
- package/dist/portal-gateway/image.js +0 -2
- package/dist/portal-gateway/imageFiles.generated.js +2 -2
- package/dist/portal-gateway/portalClient.d.ts +5 -0
- package/dist/portal-gateway/portalClient.js +4 -0
- package/dist/portal-gateway/portalService.d.ts +5 -47
- package/dist/portal-gateway/portalService.js +19 -349
- package/dist/portal-gateway/transport-worker.d.ts +1 -0
- package/dist/portal-gateway/transport-worker.js +205 -0
- package/dist/portal-gateway/transportBundle.d.ts +2 -0
- package/dist/portal-gateway/transportBundle.generated.d.ts +1 -0
- package/dist/portal-gateway/transportBundle.generated.js +2 -0
- package/dist/portal-gateway/transportBundle.js +4 -0
- package/dist/portal-gateway/upstream.d.ts +0 -81
- package/dist/portal-gateway/upstream.js +0 -1
- package/dist/sandbox/api.d.ts +121 -187
- package/dist/sandbox/api.js +5 -7
- package/dist/sandbox/client.d.ts +2 -0
- package/dist/sandbox/client.js +4 -0
- package/dist/sandbox/data-endpoints/dataEndpoints.d.ts +13 -0
- package/dist/sandbox/data-endpoints/dataEndpoints.js +100 -0
- package/dist/sandbox/data-endpoints/firewalledPorts.d.ts +4 -0
- package/dist/sandbox/data-endpoints/firewalledPorts.js +28 -0
- package/dist/sandbox/lib/httpOverStream.d.ts +3 -0
- package/dist/sandbox/lib/httpOverStream.js +21 -0
- package/dist/sandbox/lib/remoteFileOperations.d.ts +10 -0
- package/dist/sandbox/lib/remoteFileOperations.js +70 -0
- package/dist/sandbox/lib/shellCommand.d.ts +3 -0
- package/dist/sandbox/lib/shellCommand.js +7 -0
- package/dist/sandbox/providers/boat/boatService.d.ts +2 -0
- package/dist/sandbox/providers/boat/boatService.js +321 -0
- package/dist/sandbox/providers/exedev/exeDevService.d.ts +2 -0
- package/dist/sandbox/providers/exedev/exeDevService.js +127 -0
- package/dist/sandbox/providers/exedev/exeDevToken.d.ts +1 -0
- package/dist/sandbox/providers/exedev/exeDevToken.js +54 -0
- package/dist/sandbox/providers/local/localService.d.ts +2 -0
- package/dist/sandbox/providers/local/localService.js +219 -0
- package/dist/sandbox/providers/ssh/sshService.d.ts +2 -0
- package/dist/sandbox/providers/ssh/sshService.js +82 -0
- package/dist/sandbox/providers/ssh/sshTransport.d.ts +12 -0
- package/dist/sandbox/providers/ssh/sshTransport.js +216 -0
- package/dist/sandbox/service.d.ts +2 -0
- package/dist/sandbox/service.js +18 -0
- package/dist/sandbox/storage/descriptors.d.ts +6 -0
- package/dist/sandbox/storage/descriptors.js +25 -0
- package/dist/sandbox/storage/sandboxStorage.d.ts +11 -0
- package/dist/sandbox/storage/sandboxStorage.js +42 -0
- package/dist/upstreams/anthropic.d.ts +0 -5
- package/dist/upstreams/anthropic.js +1 -8
- package/dist/upstreams/index.d.ts +2 -6
- package/dist/upstreams/index.js +0 -4
- package/dist/upstreams/openai.d.ts +0 -5
- package/dist/upstreams/openai.js +1 -13
- package/package.json +12 -4
- package/dist/agent/gatewaySignIns.d.ts +0 -5
- package/dist/agent/gatewaySignIns.js +0 -11
- package/dist/agent/session/codex/home.d.ts +0 -2
- package/dist/agent/session/codex/home.js +0 -4
- package/dist/agent/session/codex/messages.d.ts +0 -30
- package/dist/agent/session/codex/messages.js +0 -135
- package/dist/agent/session/codex/transcript.d.ts +0 -12
- package/dist/agent/session/codex/transcript.js +0 -22
- package/dist/agent/session/sdkMessages.d.ts +0 -15
- package/dist/agent/session/sdkMessages.js +0 -28
- package/dist/agent/session/workerSseConnection.d.ts +0 -31
- package/dist/agent/session/workerSseConnection.js +0 -76
- package/dist/agent/vendor-types/codex/app-server/AbsolutePathBuf.d.ts +0 -10
- package/dist/agent/vendor-types/codex/app-server/AbsolutePathBuf.js +0 -2
- package/dist/agent/vendor-types/codex/app-server/FunctionCallOutputBody.d.ts +0 -2
- package/dist/agent/vendor-types/codex/app-server/FunctionCallOutputBody.js +0 -2
- package/dist/agent/vendor-types/codex/app-server/FunctionCallOutputContentItem.d.ts +0 -22
- package/dist/agent/vendor-types/codex/app-server/FunctionCallOutputContentItem.js +0 -2
- package/dist/agent/vendor-types/codex/app-server/ImageDetail.d.ts +0 -1
- package/dist/agent/vendor-types/codex/app-server/ImageDetail.js +0 -2
- package/dist/agent/vendor-types/codex/app-server/ImageGenerationFailure.d.ts +0 -5
- package/dist/agent/vendor-types/codex/app-server/ImageGenerationFailure.js +0 -2
- package/dist/agent/vendor-types/codex/app-server/ImageGenerationItem.d.ts +0 -11
- package/dist/agent/vendor-types/codex/app-server/ImageGenerationItem.js +0 -2
- package/dist/agent/vendor-types/codex/app-server/LegacyAppPathString.d.ts +0 -23
- package/dist/agent/vendor-types/codex/app-server/LegacyAppPathString.js +0 -2
- package/dist/agent/vendor-types/codex/app-server/MessagePhase.d.ts +0 -7
- package/dist/agent/vendor-types/codex/app-server/MessagePhase.js +0 -2
- package/dist/agent/vendor-types/codex/app-server/ReasoningEffort.d.ts +0 -4
- package/dist/agent/vendor-types/codex/app-server/ReasoningEffort.js +0 -2
- package/dist/agent/vendor-types/codex/app-server/SleepItem.d.ts +0 -7
- package/dist/agent/vendor-types/codex/app-server/SleepItem.js +0 -2
- package/dist/agent/vendor-types/codex/app-server/WebSearchItem.d.ts +0 -14
- package/dist/agent/vendor-types/codex/app-server/WebSearchItem.js +0 -2
- package/dist/agent/vendor-types/codex/app-server/serde_json/JsonValue.d.ts +0 -3
- package/dist/agent/vendor-types/codex/app-server/serde_json/JsonValue.js +0 -2
- package/dist/agent/vendor-types/codex/app-server/v2/AgentMessageDelivery.d.ts +0 -1
- package/dist/agent/vendor-types/codex/app-server/v2/AgentMessageDelivery.js +0 -2
- package/dist/agent/vendor-types/codex/app-server/v2/AsyncUserInputQuestion.d.ts +0 -4
- package/dist/agent/vendor-types/codex/app-server/v2/AsyncUserInputQuestion.js +0 -2
- package/dist/agent/vendor-types/codex/app-server/v2/ByteRange.d.ts +0 -4
- package/dist/agent/vendor-types/codex/app-server/v2/ByteRange.js +0 -2
- package/dist/agent/vendor-types/codex/app-server/v2/CollabAgentState.d.ts +0 -5
- package/dist/agent/vendor-types/codex/app-server/v2/CollabAgentState.js +0 -2
- package/dist/agent/vendor-types/codex/app-server/v2/CollabAgentStatus.d.ts +0 -1
- package/dist/agent/vendor-types/codex/app-server/v2/CollabAgentStatus.js +0 -2
- package/dist/agent/vendor-types/codex/app-server/v2/CollabAgentTool.d.ts +0 -1
- package/dist/agent/vendor-types/codex/app-server/v2/CollabAgentTool.js +0 -2
- package/dist/agent/vendor-types/codex/app-server/v2/CollabAgentToolCallStatus.d.ts +0 -1
- package/dist/agent/vendor-types/codex/app-server/v2/CollabAgentToolCallStatus.js +0 -2
- package/dist/agent/vendor-types/codex/app-server/v2/CommandAction.d.ts +0 -19
- package/dist/agent/vendor-types/codex/app-server/v2/CommandAction.js +0 -2
- package/dist/agent/vendor-types/codex/app-server/v2/CommandExecutionSource.d.ts +0 -1
- package/dist/agent/vendor-types/codex/app-server/v2/CommandExecutionSource.js +0 -2
- package/dist/agent/vendor-types/codex/app-server/v2/CommandExecutionStatus.d.ts +0 -1
- package/dist/agent/vendor-types/codex/app-server/v2/CommandExecutionStatus.js +0 -2
- package/dist/agent/vendor-types/codex/app-server/v2/DynamicToolCallOutputContentItem.d.ts +0 -10
- package/dist/agent/vendor-types/codex/app-server/v2/DynamicToolCallOutputContentItem.js +0 -2
- package/dist/agent/vendor-types/codex/app-server/v2/DynamicToolCallStatus.d.ts +0 -1
- package/dist/agent/vendor-types/codex/app-server/v2/DynamicToolCallStatus.js +0 -2
- package/dist/agent/vendor-types/codex/app-server/v2/FileUpdateChange.d.ts +0 -6
- package/dist/agent/vendor-types/codex/app-server/v2/FileUpdateChange.js +0 -2
- package/dist/agent/vendor-types/codex/app-server/v2/HookPromptFragment.d.ts +0 -4
- package/dist/agent/vendor-types/codex/app-server/v2/HookPromptFragment.js +0 -2
- package/dist/agent/vendor-types/codex/app-server/v2/McpAppDisplayMode.d.ts +0 -1
- package/dist/agent/vendor-types/codex/app-server/v2/McpAppDisplayMode.js +0 -2
- package/dist/agent/vendor-types/codex/app-server/v2/McpAppUi.d.ts +0 -8
- package/dist/agent/vendor-types/codex/app-server/v2/McpAppUi.js +0 -2
- package/dist/agent/vendor-types/codex/app-server/v2/McpToolCallAppContext.d.ts +0 -7
- package/dist/agent/vendor-types/codex/app-server/v2/McpToolCallAppContext.js +0 -2
- package/dist/agent/vendor-types/codex/app-server/v2/McpToolCallError.d.ts +0 -3
- package/dist/agent/vendor-types/codex/app-server/v2/McpToolCallError.js +0 -2
- package/dist/agent/vendor-types/codex/app-server/v2/McpToolCallResult.d.ts +0 -6
- package/dist/agent/vendor-types/codex/app-server/v2/McpToolCallResult.js +0 -2
- package/dist/agent/vendor-types/codex/app-server/v2/McpToolCallStatus.d.ts +0 -1
- package/dist/agent/vendor-types/codex/app-server/v2/McpToolCallStatus.js +0 -2
- package/dist/agent/vendor-types/codex/app-server/v2/MemoryCitation.d.ts +0 -5
- package/dist/agent/vendor-types/codex/app-server/v2/MemoryCitation.js +0 -2
- package/dist/agent/vendor-types/codex/app-server/v2/MemoryCitationEntry.d.ts +0 -6
- package/dist/agent/vendor-types/codex/app-server/v2/MemoryCitationEntry.js +0 -2
- package/dist/agent/vendor-types/codex/app-server/v2/PatchApplyStatus.d.ts +0 -1
- package/dist/agent/vendor-types/codex/app-server/v2/PatchApplyStatus.js +0 -2
- package/dist/agent/vendor-types/codex/app-server/v2/PatchChangeKind.d.ts +0 -8
- package/dist/agent/vendor-types/codex/app-server/v2/PatchChangeKind.js +0 -2
- package/dist/agent/vendor-types/codex/app-server/v2/SubAgentActivityKind.d.ts +0 -1
- package/dist/agent/vendor-types/codex/app-server/v2/SubAgentActivityKind.js +0 -2
- package/dist/agent/vendor-types/codex/app-server/v2/TextElement.d.ts +0 -11
- package/dist/agent/vendor-types/codex/app-server/v2/TextElement.js +0 -2
- package/dist/agent/vendor-types/codex/app-server/v2/ThreadItem.d.ts +0 -215
- package/dist/agent/vendor-types/codex/app-server/v2/ThreadItem.js +0 -2
- package/dist/agent/vendor-types/codex/app-server/v2/UserInput.d.ts +0 -35
- package/dist/agent/vendor-types/codex/app-server/v2/UserInput.js +0 -2
- package/dist/agent/vendor-types/codex/app-server/v2/WebSearchAction.d.ts +0 -14
- package/dist/agent/vendor-types/codex/app-server/v2/WebSearchAction.js +0 -2
- package/dist/cli/services.d.ts +0 -19
- package/dist/cli/services.js +0 -34
- package/dist/dimension/http.d.ts +0 -7
- package/dist/dimension/http.js +0 -77
- package/dist/dimension/storage.d.ts +0 -39
- package/dist/dimension/storage.js +0 -79
- package/dist/portal-gateway/fakes.d.ts +0 -3
- package/dist/portal-gateway/fakes.js +0 -11
- package/dist/portal-gateway/gatewayStorage.d.ts +0 -66
- package/dist/portal-gateway/gatewayStorage.js +0 -125
- package/dist/portal-gateway/portalStorage.d.ts +0 -34
- package/dist/portal-gateway/portalStorage.js +0 -44
- package/dist/sandbox/boatService.d.ts +0 -52
- package/dist/sandbox/boatService.js +0 -343
- package/dist/sandbox/containerService.d.ts +0 -15
- package/dist/sandbox/containerService.js +0 -184
- package/dist/sandbox/endpointProxy.d.ts +0 -90
- package/dist/sandbox/endpointProxy.js +0 -716
- package/dist/sandbox/exeDevAuth.d.ts +0 -16
- package/dist/sandbox/exeDevAuth.js +0 -49
- package/dist/sandbox/localService.d.ts +0 -19
- package/dist/sandbox/localService.js +0 -200
- package/dist/sandbox/sseConnection.d.ts +0 -41
- package/dist/sandbox/sseConnection.js +0 -166
- package/dist/sandbox/sshService.d.ts +0 -30
- package/dist/sandbox/sshService.js +0 -680
package/dist/dimension/paths.js
CHANGED
|
@@ -1,15 +1,24 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
export function containerName(dimensionName) {
|
|
2
|
+
return `hyb-${dimensionName}`;
|
|
3
|
+
}
|
|
3
4
|
export const WORKSPACE_PATH = "/workspace";
|
|
4
|
-
/** Where the dimension's run directory (see Storage.ensureRunDirectory) is mounted. */
|
|
5
5
|
export const RUN_PATH = "/run/hyperbrane";
|
|
6
|
-
/** Where the dimension's state directory (see Storage.ensureStateDirectory) is mounted. */
|
|
7
6
|
export const STATE_PATH = "/var/lib/hyperbrane";
|
|
8
|
-
|
|
7
|
+
export const CA_BUNDLE_PATH = "/etc/ssl/certs/ca-certificates.crt";
|
|
8
|
+
export const TRANSPORT_SOCKET = ".transport.sock";
|
|
9
9
|
export function dimensionRunDirectory(dataDirectory, dimensionName) {
|
|
10
10
|
return `${dataDirectory}/dimension-run/${dimensionName}`;
|
|
11
11
|
}
|
|
12
|
-
/** On the host, under hyperbrane's data directory: a dimension's state directory, mounted at STATE_PATH. */
|
|
13
12
|
export function dimensionStateDirectory(dataDirectory, dimensionName) {
|
|
14
13
|
return `${dataDirectory}/dimension-state/${dimensionName}`;
|
|
15
14
|
}
|
|
15
|
+
export const dimensionLayout = {
|
|
16
|
+
containerName,
|
|
17
|
+
workspacePath: () => WORKSPACE_PATH,
|
|
18
|
+
runPath: () => RUN_PATH,
|
|
19
|
+
statePath: () => STATE_PATH,
|
|
20
|
+
caBundlePath: () => CA_BUNDLE_PATH,
|
|
21
|
+
runDirectory: dimensionRunDirectory,
|
|
22
|
+
stateDirectory: dimensionStateDirectory,
|
|
23
|
+
transportSocketPath: (dataDirectory, dimensionName) => `${dimensionRunDirectory(dataDirectory, dimensionName)}/${TRANSPORT_SOCKET}`,
|
|
24
|
+
};
|
|
@@ -1,26 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { type
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
import type { Storage } from "./storage.ts";
|
|
6
|
-
import { RUN_PATH, STATE_PATH, WORKSPACE_PATH } from "./paths.ts";
|
|
7
|
-
import type { PortalService } from "../portal-gateway/portalService.ts";
|
|
8
|
-
export { RUN_PATH, STATE_PATH, WORKSPACE_PATH };
|
|
1
|
+
import type { Sandbox } from "../sandbox/api.ts";
|
|
2
|
+
import { type DimensionClient } from "./api.ts";
|
|
3
|
+
import { RUN_PATH, STATE_PATH, WORKSPACE_PATH, containerName } from "./paths.ts";
|
|
4
|
+
export { RUN_PATH, STATE_PATH, WORKSPACE_PATH, containerName };
|
|
9
5
|
/** @sealed */
|
|
10
|
-
export interface DimensionService {
|
|
11
|
-
getDimensions(): Promise<DimensionInfo[]>;
|
|
12
|
-
/** The dimensions' names, from the records alone: none of getDimensions' container status and usage. */
|
|
13
|
-
getDimensionNames(): Promise<string[]>;
|
|
14
|
-
createDimension(dimension: Dimension): Promise<void>;
|
|
15
|
-
destroyDimension(dimensionName: string): Promise<void>;
|
|
16
|
-
restartDimension(dimensionName: string): Promise<void>;
|
|
17
|
-
updateDimension(dimensionName: string, limits: DimensionLimits): Promise<Dimension>;
|
|
6
|
+
export interface DimensionService extends DimensionClient {
|
|
18
7
|
}
|
|
19
|
-
export declare function
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
* (bind-mount sources) resolve the same way docker resolves them.
|
|
23
|
-
*/
|
|
24
|
-
export declare function createDimensionService(storage: Storage, docker: DockerClient, image: DimensionImage, host: Sandbox,
|
|
25
|
-
/** Re-wires the portals of a dimension whose container restarted (portal-gateway/portalService.ts). */
|
|
26
|
-
restoring?: Pick<PortalService, "restorePortals">): DimensionService;
|
|
8
|
+
export declare function createDimensionService(host: Sandbox, { dataDirectory }?: {
|
|
9
|
+
dataDirectory?: string;
|
|
10
|
+
}): DimensionService;
|
|
@@ -1,246 +1,78 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
export { RUN_PATH, STATE_PATH, WORKSPACE_PATH };
|
|
9
|
-
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
/** Re-wires the portals of a dimension whose container restarted (portal-gateway/portalService.ts). */
|
|
20
|
-
restoring) {
|
|
21
|
-
async function getDimensions() {
|
|
22
|
-
return Promise.all((await storage.getDimensions()).map(describeDimension));
|
|
23
|
-
}
|
|
24
|
-
async function describeDimension({ state, ...dimension }) {
|
|
25
|
-
if (state === "creating")
|
|
26
|
-
return withoutUsage(dimension, "creating");
|
|
27
|
-
const container = await docker.inspectContainer(containerName(dimension.name));
|
|
28
|
-
if (!container)
|
|
29
|
-
return withoutUsage(dimension, "missing");
|
|
30
|
-
const containerState = container.State;
|
|
31
|
-
if (!containerState?.Running || !containerState.StartedAt) {
|
|
32
|
-
return withoutUsage(dimension, containerState?.Status ?? "exited");
|
|
33
|
-
}
|
|
34
|
-
try {
|
|
35
|
-
const stats = await docker.containerStats(containerName(dimension.name));
|
|
36
|
-
return {
|
|
37
|
-
...dimension,
|
|
38
|
-
status: "running",
|
|
39
|
-
uptimeSeconds: Math.max(0, Math.floor((Date.now() - Date.parse(containerState.StartedAt)) / 1000)),
|
|
40
|
-
memoryUsage: stats.memoryUsage,
|
|
41
|
-
cpuUsage: stats.cpuUsage,
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
catch (error) {
|
|
45
|
-
if (error instanceof DockerError && error.status === 404)
|
|
46
|
-
return withoutUsage(dimension, "missing");
|
|
47
|
-
throw error;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
// Keeps no state between calls: every step re-reads storage or docker and uses operations that are
|
|
51
|
-
// atomic across processes, so a create that crashed halfway, or runs twice at once, converges.
|
|
52
|
-
// A relative directory ("work", "~/work") is the host user's: resolved against their home, and
|
|
53
|
-
// recorded resolved. A missing one is made.
|
|
54
|
-
async function createDimension(requested) {
|
|
55
|
-
assertValidName(requested.name);
|
|
56
|
-
const dimension = { ...requested, directory: await resolveDirectory(requested.directory) };
|
|
57
|
-
const owner = await directoryOwner(dimension.directory);
|
|
58
|
-
const existing = await docker.inspectContainer(containerName(dimension.name));
|
|
59
|
-
if (existing)
|
|
60
|
-
assertOwnedContainer(existing, dimension.name);
|
|
61
|
-
const record = await storage.insertDimension({ ...dimension, state: "creating" });
|
|
62
|
-
assertSameSpec(record, dimension);
|
|
63
|
-
const imageTag = await image.ensure();
|
|
64
|
-
const runDirectory = await storage.ensureRunDirectory(dimension.name);
|
|
65
|
-
const stateDirectory = await storage.ensureStateDirectory(dimension.name);
|
|
66
|
-
await ensureContainer(dimension, owner, imageTag, runDirectory, stateDirectory);
|
|
67
|
-
await docker.startContainer(containerName(dimension.name));
|
|
68
|
-
await ensureUser(dimension, owner);
|
|
69
|
-
if (record.state !== "ready")
|
|
70
|
-
await storage.saveDimension({ ...dimension, state: "ready" });
|
|
71
|
-
}
|
|
72
|
-
async function ensureContainer(dimension, owner, imageTag, runDirectory, stateDirectory) {
|
|
73
|
-
const name = containerName(dimension.name);
|
|
74
|
-
const existing = await docker.inspectContainer(name);
|
|
75
|
-
if (existing)
|
|
76
|
-
return assertOwnedContainer(existing, dimension.name);
|
|
77
|
-
try {
|
|
78
|
-
await docker.createContainer(name, {
|
|
79
|
-
Image: imageTag,
|
|
80
|
-
Cmd: ["sleep", "infinity"],
|
|
81
|
-
Hostname: name,
|
|
82
|
-
User: `${owner.uid}:${owner.gid}`,
|
|
83
|
-
WorkingDir: WORKSPACE_PATH,
|
|
84
|
-
Labels: {
|
|
85
|
-
hyperbrane: "true",
|
|
86
|
-
"hyperbrane.version": HYPERBRANE_VERSION,
|
|
87
|
-
"hyperbrane.dimension": dimension.name,
|
|
88
|
-
},
|
|
89
|
-
HostConfig: {
|
|
90
|
-
Binds: [`${dimension.directory}:${WORKSPACE_PATH}`, `${runDirectory}:${RUN_PATH}`, `${stateDirectory}:${STATE_PATH}`],
|
|
91
|
-
// dmesg: docker drops CAP_SYSLOG, and its seccomp profile refuses the syslog call without it.
|
|
92
|
-
// The log is the host's kernel log, shared by every dimension.
|
|
93
|
-
CapAdd: ["SYSLOG"],
|
|
94
|
-
...containerResources(dimension),
|
|
95
|
-
NetworkMode: "none",
|
|
96
|
-
// Without a network docker doesn't map the hostname, and sudo warns on every call.
|
|
97
|
-
ExtraHosts: [`${name}:127.0.1.1`],
|
|
98
|
-
Init: true,
|
|
99
|
-
RestartPolicy: { Name: "unless-stopped" },
|
|
100
|
-
},
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
catch (error) {
|
|
104
|
-
// Lost a race with another creator: fine as long as the winner is ours.
|
|
105
|
-
if (!(error instanceof DockerError && error.status === 409))
|
|
106
|
-
throw error;
|
|
107
|
-
assertOwnedContainer(await awaitContainer(name, error), dimension.name);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
// Docker reserves the name before the winner's container becomes inspectable, so give it a moment.
|
|
111
|
-
async function awaitContainer(name, conflict) {
|
|
112
|
-
for (let attempt = 0; attempt < 50; attempt++) {
|
|
113
|
-
const container = await docker.inspectContainer(name);
|
|
114
|
-
if (container)
|
|
115
|
-
return container;
|
|
116
|
-
await sleep(100);
|
|
117
|
-
}
|
|
118
|
-
throw conflict;
|
|
1
|
+
import { DimensionInvalidError, isDimensionName } from "./api.js";
|
|
2
|
+
import { createDimensionScript, destroyDimensionScript, dimensionUsageScript, listDimensionsScript, restartDimensionScript, startTransportScript, updateDimensionScript, } from "./control.js";
|
|
3
|
+
import { createDimensionImage } from "./image.js";
|
|
4
|
+
import { RUN_PATH, STATE_PATH, WORKSPACE_PATH, containerName, dimensionLayout } from "./paths.js";
|
|
5
|
+
import { transportFiles } from "./transportClient.js";
|
|
6
|
+
import { createDockerClient } from "../lib/docker.js";
|
|
7
|
+
import { createPortalClient } from "../portal-gateway/portalClient.js";
|
|
8
|
+
export { RUN_PATH, STATE_PATH, WORKSPACE_PATH, containerName };
|
|
9
|
+
export function createDimensionService(host, { dataDirectory } = {}) {
|
|
10
|
+
const options = { dataDirectory };
|
|
11
|
+
const image = createDimensionImage(createDockerClient(host));
|
|
12
|
+
const portals = createPortalClient(host, { dataDirectory });
|
|
13
|
+
async function listDimensions() {
|
|
14
|
+
return (await listDimensionsScript(host, options)).map(({ record: { state: _state, ...dimension }, status, startedAt }) => ({
|
|
15
|
+
...dimension,
|
|
16
|
+
status,
|
|
17
|
+
uptimeSeconds: startedAt ? Math.max(0, Math.floor((Date.now() - Date.parse(startedAt)) / 1000)) : null,
|
|
18
|
+
}));
|
|
119
19
|
}
|
|
120
|
-
async function
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
20
|
+
async function createDimension(dimension) {
|
|
21
|
+
assertValidName(dimension.name);
|
|
22
|
+
if (!dimension.directory?.trim())
|
|
23
|
+
throw new DimensionInvalidError("directory is required");
|
|
24
|
+
if (/[\x00-\x1f]/.test(dimension.directory))
|
|
25
|
+
throw new DimensionInvalidError("directory has a control character");
|
|
26
|
+
assertLimits({ memoryLimit: dimension.memoryLimit, cpuLimit: dimension.cpuLimit });
|
|
27
|
+
const record = (await createDimensionScript(host, dimension, options)) || (await image.ensure(), await createDimensionScript(host, dimension, options));
|
|
28
|
+
if (!record)
|
|
29
|
+
throw new Error(`the dimension image is missing after it was built`);
|
|
30
|
+
await startTransportScript(host, dimension.name, options, { restart: false });
|
|
31
|
+
const { state: _state, ...created } = record;
|
|
32
|
+
return created;
|
|
129
33
|
}
|
|
130
|
-
// Container first: if this crashes before the record is removed, the dimension still shows up
|
|
131
|
-
// (as "missing") and the destroy can be retried. Its run and state directories go with the record,
|
|
132
|
-
// emptied from inside first: what a dimension running as root left there (an agent worker's socket
|
|
133
|
-
// and log) the host user couldn't remove.
|
|
134
34
|
async function destroyDimension(dimensionName) {
|
|
135
35
|
assertValidName(dimensionName);
|
|
136
|
-
|
|
137
|
-
const existing = await docker.inspectContainer(name);
|
|
138
|
-
if (existing) {
|
|
139
|
-
assertOwnedContainer(existing, dimensionName);
|
|
140
|
-
if (!existing.State?.Running)
|
|
141
|
-
await docker.startContainer(name).catch(() => { });
|
|
142
|
-
// Best effort: if it fails, removing them from the host below says why.
|
|
143
|
-
await docker
|
|
144
|
-
.exec(name, { cmd: ["sh", "-c", 'find "$R" "$S" -mindepth 1 -delete'], user: "root", env: { R: RUN_PATH, S: STATE_PATH } })
|
|
145
|
-
.catch(() => { });
|
|
146
|
-
await docker.removeContainer(name);
|
|
147
|
-
}
|
|
148
|
-
await storage.removeRunDirectory(dimensionName);
|
|
149
|
-
await storage.removeStateDirectory(dimensionName);
|
|
150
|
-
await storage.removeDimension(dimensionName);
|
|
36
|
+
await destroyDimensionScript(host, dimensionName, options);
|
|
151
37
|
}
|
|
152
|
-
// Same container, so the user and anything installed outside the workspace survive; the workspace
|
|
153
|
-
// is a bind mount either way.
|
|
154
38
|
async function restartDimension(dimensionName) {
|
|
155
39
|
assertValidName(dimensionName);
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
}
|
|
159
|
-
const existing = await docker.inspectContainer(containerName(dimensionName));
|
|
160
|
-
if (!existing)
|
|
161
|
-
throw new DimensionNotFoundError(`dimension ${dimensionName} has no container; create it again`);
|
|
162
|
-
assertOwnedContainer(existing, dimensionName);
|
|
163
|
-
await docker.restartContainer(containerName(dimensionName));
|
|
164
|
-
// The restart took its end of every portal with its network namespace.
|
|
165
|
-
await restoring?.restorePortals({ dimensionName });
|
|
40
|
+
await restartDimensionScript(host, dimensionName, options);
|
|
41
|
+
await portals.restorePortals({ dimensionName });
|
|
42
|
+
await startTransportScript(host, dimensionName, options, { restart: true });
|
|
166
43
|
}
|
|
167
|
-
|
|
44
|
+
async function transportSocket(dimensionName) {
|
|
45
|
+
assertValidName(dimensionName);
|
|
46
|
+
const data = dataDirectory ?? `${await host.homeDirectory()}/.local/share/hyperbrane`;
|
|
47
|
+
return dimensionLayout.transportSocketPath(data, dimensionName);
|
|
48
|
+
}
|
|
49
|
+
const files = transportFiles(async (dimensionName) => host.connect(await transportSocket(dimensionName)));
|
|
168
50
|
async function updateDimension(dimensionName, limits) {
|
|
169
51
|
assertValidName(dimensionName);
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
throw new DimensionInvalidError(`${name} must be positive`);
|
|
173
|
-
}
|
|
174
|
-
const record = (await storage.getDimensions()).find((dimension) => dimension.name === dimensionName);
|
|
175
|
-
if (!record)
|
|
176
|
-
throw new DimensionNotFoundError(`dimension ${dimensionName} does not exist`);
|
|
177
|
-
const existing = await docker.inspectContainer(containerName(dimensionName));
|
|
178
|
-
if (!existing)
|
|
179
|
-
throw new DimensionNotFoundError(`dimension ${dimensionName} has no container; create it again`);
|
|
180
|
-
assertOwnedContainer(existing, dimensionName);
|
|
181
|
-
const { state, ...current } = record;
|
|
182
|
-
const updated = {
|
|
183
|
-
...current,
|
|
184
|
-
memoryLimit: limits.memoryLimit ?? record.memoryLimit,
|
|
185
|
-
cpuLimit: limits.cpuLimit ?? record.cpuLimit,
|
|
186
|
-
};
|
|
187
|
-
await docker.updateContainer(containerName(dimensionName), containerResources(updated));
|
|
188
|
-
await storage.saveDimension({ ...updated, state });
|
|
52
|
+
assertLimits(limits);
|
|
53
|
+
const { state: _state, ...updated } = await updateDimensionScript(host, dimensionName, limits, options);
|
|
189
54
|
return updated;
|
|
190
55
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
throw new DimensionInvalidError(`directory ${directory} doesn't exist and can't be made: ${cause.message}`);
|
|
208
|
-
});
|
|
209
|
-
return host.stat(directory);
|
|
210
|
-
});
|
|
211
|
-
if (info.type !== "directory")
|
|
212
|
-
throw new DimensionInvalidError(`not a directory: ${directory}`);
|
|
213
|
-
return { uid: info.uid, gid: info.gid };
|
|
214
|
-
}
|
|
215
|
-
async function getDimensionNames() {
|
|
216
|
-
return (await storage.getDimensions()).map((dimension) => dimension.name);
|
|
56
|
+
return {
|
|
57
|
+
listDimensions,
|
|
58
|
+
getDimensionUsage: () => dimensionUsageScript(host, options),
|
|
59
|
+
readFile: files.readFile,
|
|
60
|
+
writeFile: files.writeFile,
|
|
61
|
+
statFile: files.statFile,
|
|
62
|
+
createDimension: (dimension) => createDimension(dimension),
|
|
63
|
+
destroyDimension: (dimensionName) => destroyDimension(dimensionName),
|
|
64
|
+
restartDimension: (dimensionName) => restartDimension(dimensionName),
|
|
65
|
+
updateDimension: (dimensionName, limits) => updateDimension(dimensionName, limits),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function assertLimits(limits) {
|
|
69
|
+
for (const [name, value] of Object.entries(limits)) {
|
|
70
|
+
if (value !== undefined && !(typeof value === "number" && value > 0))
|
|
71
|
+
throw new DimensionInvalidError(`${name} must be positive`);
|
|
217
72
|
}
|
|
218
|
-
return { getDimensions, getDimensionNames, createDimension, destroyDimension, restartDimension, updateDimension };
|
|
219
73
|
}
|
|
220
74
|
function assertValidName(dimensionName) {
|
|
221
|
-
if (!
|
|
75
|
+
if (!isDimensionName(dimensionName)) {
|
|
222
76
|
throw new DimensionInvalidError(`invalid dimension name "${dimensionName}": use letters, digits, "_", "." or "-", starting with a letter or digit`);
|
|
223
77
|
}
|
|
224
78
|
}
|
|
225
|
-
function assertSameSpec(existing, requested) {
|
|
226
|
-
const differs = existing.directory !== requested.directory ||
|
|
227
|
-
existing.memoryLimit !== requested.memoryLimit ||
|
|
228
|
-
existing.cpuLimit !== requested.cpuLimit ||
|
|
229
|
-
[...(existing.portals ?? [])].sort().join("\0") !== [...(requested.portals ?? [])].sort().join("\0");
|
|
230
|
-
if (differs) {
|
|
231
|
-
throw new DimensionConflictError(`dimension ${requested.name} already exists with a different spec`);
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
function assertOwnedContainer(container, dimensionName) {
|
|
235
|
-
if (container.Config?.Labels?.["hyperbrane.dimension"] !== dimensionName) {
|
|
236
|
-
throw new DimensionConflictError(`container ${containerName(dimensionName)} exists but is not managed by hyperbrane`);
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
// Swap stays at twice the memory, which is what docker gives a container created with only a
|
|
240
|
-
// memory limit; setting both keeps an update valid whichever way the limit moves.
|
|
241
|
-
function containerResources({ memoryLimit, cpuLimit }) {
|
|
242
|
-
return { Memory: memoryLimit, MemorySwap: memoryLimit * 2, NanoCpus: Math.round(cpuLimit * 1e9) };
|
|
243
|
-
}
|
|
244
|
-
function withoutUsage(dimension, status) {
|
|
245
|
-
return { ...dimension, status, uptimeSeconds: null, memoryUsage: null, cpuUsage: null };
|
|
246
|
-
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { constants } from "node:fs";
|
|
2
|
+
import { chown, mkdir, open, realpath, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
3
|
+
import { createServer } from "node:net";
|
|
4
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
5
|
+
import { UnpackrStream } from "msgpackr";
|
|
6
|
+
import { TRANSPORT_CHUNK, TRANSPORT_MSGPACK, transportPackr } from "./transportProtocol.js";
|
|
7
|
+
class TransportFailure extends Error {
|
|
8
|
+
name;
|
|
9
|
+
constructor(name, message) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.name = name;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
function required(name) {
|
|
15
|
+
const value = process.env[name];
|
|
16
|
+
if (!value)
|
|
17
|
+
throw new Error(`${name} is not set`);
|
|
18
|
+
return value;
|
|
19
|
+
}
|
|
20
|
+
async function existingAncestor(path) {
|
|
21
|
+
for (let current = path;; current = dirname(current)) {
|
|
22
|
+
try {
|
|
23
|
+
return await realpath(current);
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
if (error.code !== "ENOENT" || current === "/")
|
|
27
|
+
throw error;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function assertInside(real, path) {
|
|
32
|
+
if (real !== workspace && !real.startsWith(`${workspace}/`))
|
|
33
|
+
throw new TransportFailure("DimensionInvalidError", `"${path}" is outside the workspace`);
|
|
34
|
+
}
|
|
35
|
+
async function workspacePath(path) {
|
|
36
|
+
const full = resolve(workspace, path.replace(/^\/+/, ""));
|
|
37
|
+
assertInside(await existingAncestor(full), path);
|
|
38
|
+
return full;
|
|
39
|
+
}
|
|
40
|
+
function checkUpload(upload) {
|
|
41
|
+
if (!/^[A-Za-z0-9-]{1,64}$/.test(upload))
|
|
42
|
+
throw new TransportFailure("DimensionInvalidError", "the upload id must be 1-64 letters, digits or dashes");
|
|
43
|
+
}
|
|
44
|
+
async function stagingPath(path, upload) {
|
|
45
|
+
checkUpload(upload);
|
|
46
|
+
const target = await workspacePath(path);
|
|
47
|
+
return join(dirname(target), `.${basename(target)}.${upload}.upload`);
|
|
48
|
+
}
|
|
49
|
+
function notFound(path) {
|
|
50
|
+
return (error) => {
|
|
51
|
+
if (error.code === "ENOENT")
|
|
52
|
+
throw new TransportFailure("DimensionNotFoundError", `no file "${path}" in the workspace`);
|
|
53
|
+
throw error;
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
async function handle(request, send) {
|
|
57
|
+
switch (request.op) {
|
|
58
|
+
case "stat": {
|
|
59
|
+
const info = await stat(await workspacePath(request.path)).catch(notFound(request.path));
|
|
60
|
+
const type = info.isFile() ? "file" : info.isDirectory() ? "directory" : "other";
|
|
61
|
+
return send({ id: request.id, info: { type, size: info.size } });
|
|
62
|
+
}
|
|
63
|
+
case "read": {
|
|
64
|
+
const file = await open(await workspacePath(request.path), "r").catch(notFound(request.path));
|
|
65
|
+
try {
|
|
66
|
+
const offset = request.offset ?? 0;
|
|
67
|
+
const end = request.length === undefined ? (await file.stat()).size : offset + request.length;
|
|
68
|
+
for (let position = offset; position < end;) {
|
|
69
|
+
const buffer = new Uint8Array(Math.min(TRANSPORT_CHUNK, end - position));
|
|
70
|
+
const { bytesRead } = await file.read(buffer, 0, buffer.length, position);
|
|
71
|
+
if (bytesRead === 0)
|
|
72
|
+
break;
|
|
73
|
+
await send({ id: request.id, data: buffer.subarray(0, bytesRead) });
|
|
74
|
+
position += bytesRead;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
finally {
|
|
78
|
+
await file.close();
|
|
79
|
+
}
|
|
80
|
+
return send({ id: request.id, done: true });
|
|
81
|
+
}
|
|
82
|
+
case "staged": {
|
|
83
|
+
const staged = await stat(await stagingPath(request.path, request.upload)).catch(() => null);
|
|
84
|
+
return send({ id: request.id, size: staged?.size ?? 0 });
|
|
85
|
+
}
|
|
86
|
+
case "write": {
|
|
87
|
+
const staging = await stagingPath(request.path, request.upload);
|
|
88
|
+
await mkdir(dirname(staging), { recursive: true });
|
|
89
|
+
assertInside(await realpath(dirname(staging)), request.path);
|
|
90
|
+
const file = await open(staging, constants.O_CREAT | constants.O_WRONLY, 0o666);
|
|
91
|
+
try {
|
|
92
|
+
await file.write(request.data, 0, request.data.length, request.offset);
|
|
93
|
+
}
|
|
94
|
+
finally {
|
|
95
|
+
await file.close();
|
|
96
|
+
}
|
|
97
|
+
return send({ id: request.id });
|
|
98
|
+
}
|
|
99
|
+
case "commit": {
|
|
100
|
+
const staging = await stagingPath(request.path, request.upload);
|
|
101
|
+
const staged = await stat(staging).catch(notFound(request.path));
|
|
102
|
+
if (staged.size < request.size)
|
|
103
|
+
throw new TransportFailure("DimensionInvalidError", `the upload of "${request.path}" has ${staged.size} of ${request.size} bytes`);
|
|
104
|
+
const file = await open(staging, "r+");
|
|
105
|
+
try {
|
|
106
|
+
await file.truncate(request.size);
|
|
107
|
+
}
|
|
108
|
+
finally {
|
|
109
|
+
await file.close();
|
|
110
|
+
}
|
|
111
|
+
await rename(staging, await workspacePath(request.path));
|
|
112
|
+
return send({ id: request.id });
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
function serve(socket) {
|
|
117
|
+
const send = (reply) => new Promise((done) => {
|
|
118
|
+
if (socket.destroyed || socket.write(transportPackr.pack(reply)))
|
|
119
|
+
return done();
|
|
120
|
+
socket.once("drain", done);
|
|
121
|
+
socket.once("close", done);
|
|
122
|
+
});
|
|
123
|
+
const decoder = new UnpackrStream(TRANSPORT_MSGPACK);
|
|
124
|
+
decoder.on("data", (request) => {
|
|
125
|
+
handle(request, send).catch((error) => {
|
|
126
|
+
const { name, message } = error instanceof Error ? error : new Error(String(error));
|
|
127
|
+
return send({ id: request.id, error: { name, message } });
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
decoder.on("error", () => socket.destroy());
|
|
131
|
+
socket.on("error", () => socket.destroy());
|
|
132
|
+
socket.pipe(decoder);
|
|
133
|
+
}
|
|
134
|
+
const workspace = await realpath(required("HYB_WORKSPACE"));
|
|
135
|
+
const socketPath = required("HYB_TRANSPORT_SOCKET");
|
|
136
|
+
const build = required("HYB_TRANSPORT_BUILD");
|
|
137
|
+
const owner = process.env.HYB_SOCKET_OWNER;
|
|
138
|
+
await rm(socketPath, { force: true });
|
|
139
|
+
const server = createServer(serve);
|
|
140
|
+
await new Promise((done) => server.listen(socketPath, done));
|
|
141
|
+
if (owner) {
|
|
142
|
+
const [uid, gid] = owner.split(":").map(Number);
|
|
143
|
+
await chown(socketPath, uid, gid);
|
|
144
|
+
}
|
|
145
|
+
for (const signal of ["SIGTERM", "SIGINT"]) {
|
|
146
|
+
process.once(signal, () => {
|
|
147
|
+
server.close();
|
|
148
|
+
process.exit(0);
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
await writeFile(required("HYB_TRANSPORT_READY"), `${process.pid} ${build}\n`);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const DIMENSION_TRANSPORT_SOURCE: string;
|