hyperbrane 0.1.1 → 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 +81 -11
- package/dist/agent/agentClient.d.ts +5 -0
- package/dist/agent/agentClient.js +4 -0
- package/dist/agent/agentService.d.ts +9 -30
- package/dist/agent/agentService.js +62 -293
- package/dist/agent/agentStorage.d.ts +1 -20
- package/dist/agent/agentStorage.js +0 -30
- package/dist/agent/api.d.ts +89 -133
- package/dist/agent/api.js +4 -4
- 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 -30
- 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 +27 -18
- package/dist/agent/session/worker/protocol.js +32 -16
- package/dist/agent/session/worker/workerParams.d.ts +0 -5
- package/dist/agent/session/worker/workerParams.js +15 -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 +44 -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 -21
- package/dist/dimension/service.js +60 -224
- 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 -8
- package/dist/index.js +5 -16
- 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 -35
- package/dist/portal-gateway/gatewayService.js +79 -253
- package/dist/portal-gateway/image.d.ts +1 -2
- package/dist/portal-gateway/image.js +1 -3
- package/dist/portal-gateway/imageFiles.generated.js +3 -3
- 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 -317
- 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 +12 -82
- package/dist/portal-gateway/upstream.js +0 -1
- package/dist/sandbox/api.d.ts +121 -178
- 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 -15
- package/dist/agent/session/workerSseConnection.js +0 -26
- 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 -33
- 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 -701
- 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
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import type { DimensionLayout } from "./api.ts";
|
|
2
|
+
export declare function containerName(dimensionName: string): string;
|
|
1
3
|
export declare const WORKSPACE_PATH = "/workspace";
|
|
2
|
-
/** Where the dimension's run directory (see Storage.ensureRunDirectory) is mounted. */
|
|
3
4
|
export declare const RUN_PATH = "/run/hyperbrane";
|
|
4
|
-
/** Where the dimension's state directory (see Storage.ensureStateDirectory) is mounted. */
|
|
5
5
|
export declare const STATE_PATH = "/var/lib/hyperbrane";
|
|
6
|
-
|
|
6
|
+
export declare const CA_BUNDLE_PATH = "/etc/ssl/certs/ca-certificates.crt";
|
|
7
|
+
export declare const TRANSPORT_SOCKET = ".transport.sock";
|
|
7
8
|
export declare function dimensionRunDirectory(dataDirectory: string, dimensionName: string): string;
|
|
8
|
-
/** On the host, under hyperbrane's data directory: a dimension's state directory, mounted at STATE_PATH. */
|
|
9
9
|
export declare function dimensionStateDirectory(dataDirectory: string, dimensionName: string): string;
|
|
10
|
+
export declare const dimensionLayout: DimensionLayout;
|
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,23 +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
|
-
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 };
|
|
8
5
|
/** @sealed */
|
|
9
|
-
export interface DimensionService {
|
|
10
|
-
getDimensions(): Promise<DimensionInfo[]>;
|
|
11
|
-
/** The dimensions' names, from the records alone: none of getDimensions' container status and usage. */
|
|
12
|
-
getDimensionNames(): Promise<string[]>;
|
|
13
|
-
createDimension(dimension: Dimension): Promise<void>;
|
|
14
|
-
destroyDimension(dimensionName: string): Promise<void>;
|
|
15
|
-
restartDimension(dimensionName: string): Promise<void>;
|
|
16
|
-
updateDimension(dimensionName: string, limits: DimensionLimits): Promise<Dimension>;
|
|
6
|
+
export interface DimensionService extends DimensionClient {
|
|
17
7
|
}
|
|
18
|
-
export declare function
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
* (bind-mount sources) resolve the same way docker resolves them.
|
|
22
|
-
*/
|
|
23
|
-
export declare function createDimensionService(storage: Storage, docker: DockerClient, image: DimensionImage, host: Sandbox): DimensionService;
|
|
8
|
+
export declare function createDimensionService(host: Sandbox, { dataDirectory }?: {
|
|
9
|
+
dataDirectory?: string;
|
|
10
|
+
}): DimensionService;
|
|
@@ -1,242 +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
|
-
async function getDimensions() {
|
|
20
|
-
return Promise.all((await storage.getDimensions()).map(describeDimension));
|
|
21
|
-
}
|
|
22
|
-
async function describeDimension({ state, ...dimension }) {
|
|
23
|
-
if (state === "creating")
|
|
24
|
-
return withoutUsage(dimension, "creating");
|
|
25
|
-
const container = await docker.inspectContainer(containerName(dimension.name));
|
|
26
|
-
if (!container)
|
|
27
|
-
return withoutUsage(dimension, "missing");
|
|
28
|
-
const containerState = container.State;
|
|
29
|
-
if (!containerState?.Running || !containerState.StartedAt) {
|
|
30
|
-
return withoutUsage(dimension, containerState?.Status ?? "exited");
|
|
31
|
-
}
|
|
32
|
-
try {
|
|
33
|
-
const stats = await docker.containerStats(containerName(dimension.name));
|
|
34
|
-
return {
|
|
35
|
-
...dimension,
|
|
36
|
-
status: "running",
|
|
37
|
-
uptimeSeconds: Math.max(0, Math.floor((Date.now() - Date.parse(containerState.StartedAt)) / 1000)),
|
|
38
|
-
memoryUsage: stats.memoryUsage,
|
|
39
|
-
cpuUsage: stats.cpuUsage,
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
catch (error) {
|
|
43
|
-
if (error instanceof DockerError && error.status === 404)
|
|
44
|
-
return withoutUsage(dimension, "missing");
|
|
45
|
-
throw error;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
// Keeps no state between calls: every step re-reads storage or docker and uses operations that are
|
|
49
|
-
// atomic across processes, so a create that crashed halfway, or runs twice at once, converges.
|
|
50
|
-
// A relative directory ("work", "~/work") is the host user's: resolved against their home, and
|
|
51
|
-
// recorded resolved. A missing one is made.
|
|
52
|
-
async function createDimension(requested) {
|
|
53
|
-
assertValidName(requested.name);
|
|
54
|
-
const dimension = { ...requested, directory: await resolveDirectory(requested.directory) };
|
|
55
|
-
const owner = await directoryOwner(dimension.directory);
|
|
56
|
-
const existing = await docker.inspectContainer(containerName(dimension.name));
|
|
57
|
-
if (existing)
|
|
58
|
-
assertOwnedContainer(existing, dimension.name);
|
|
59
|
-
const record = await storage.insertDimension({ ...dimension, state: "creating" });
|
|
60
|
-
assertSameSpec(record, dimension);
|
|
61
|
-
const imageTag = await image.ensure();
|
|
62
|
-
const runDirectory = await storage.ensureRunDirectory(dimension.name);
|
|
63
|
-
const stateDirectory = await storage.ensureStateDirectory(dimension.name);
|
|
64
|
-
await ensureContainer(dimension, owner, imageTag, runDirectory, stateDirectory);
|
|
65
|
-
await docker.startContainer(containerName(dimension.name));
|
|
66
|
-
await ensureUser(dimension, owner);
|
|
67
|
-
if (record.state !== "ready")
|
|
68
|
-
await storage.saveDimension({ ...dimension, state: "ready" });
|
|
69
|
-
}
|
|
70
|
-
async function ensureContainer(dimension, owner, imageTag, runDirectory, stateDirectory) {
|
|
71
|
-
const name = containerName(dimension.name);
|
|
72
|
-
const existing = await docker.inspectContainer(name);
|
|
73
|
-
if (existing)
|
|
74
|
-
return assertOwnedContainer(existing, dimension.name);
|
|
75
|
-
try {
|
|
76
|
-
await docker.createContainer(name, {
|
|
77
|
-
Image: imageTag,
|
|
78
|
-
Cmd: ["sleep", "infinity"],
|
|
79
|
-
Hostname: name,
|
|
80
|
-
User: `${owner.uid}:${owner.gid}`,
|
|
81
|
-
WorkingDir: WORKSPACE_PATH,
|
|
82
|
-
Labels: {
|
|
83
|
-
hyperbrane: "true",
|
|
84
|
-
"hyperbrane.version": HYPERBRANE_VERSION,
|
|
85
|
-
"hyperbrane.dimension": dimension.name,
|
|
86
|
-
},
|
|
87
|
-
HostConfig: {
|
|
88
|
-
Binds: [`${dimension.directory}:${WORKSPACE_PATH}`, `${runDirectory}:${RUN_PATH}`, `${stateDirectory}:${STATE_PATH}`],
|
|
89
|
-
// dmesg: docker drops CAP_SYSLOG, and its seccomp profile refuses the syslog call without it.
|
|
90
|
-
// The log is the host's kernel log, shared by every dimension.
|
|
91
|
-
CapAdd: ["SYSLOG"],
|
|
92
|
-
...containerResources(dimension),
|
|
93
|
-
NetworkMode: "none",
|
|
94
|
-
// Without a network docker doesn't map the hostname, and sudo warns on every call.
|
|
95
|
-
ExtraHosts: [`${name}:127.0.1.1`],
|
|
96
|
-
Init: true,
|
|
97
|
-
RestartPolicy: { Name: "unless-stopped" },
|
|
98
|
-
},
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
catch (error) {
|
|
102
|
-
// Lost a race with another creator: fine as long as the winner is ours.
|
|
103
|
-
if (!(error instanceof DockerError && error.status === 409))
|
|
104
|
-
throw error;
|
|
105
|
-
assertOwnedContainer(await awaitContainer(name, error), dimension.name);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
// Docker reserves the name before the winner's container becomes inspectable, so give it a moment.
|
|
109
|
-
async function awaitContainer(name, conflict) {
|
|
110
|
-
for (let attempt = 0; attempt < 50; attempt++) {
|
|
111
|
-
const container = await docker.inspectContainer(name);
|
|
112
|
-
if (container)
|
|
113
|
-
return container;
|
|
114
|
-
await sleep(100);
|
|
115
|
-
}
|
|
116
|
-
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
|
+
}));
|
|
117
19
|
}
|
|
118
|
-
async function
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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;
|
|
127
33
|
}
|
|
128
|
-
// Container first: if this crashes before the record is removed, the dimension still shows up
|
|
129
|
-
// (as "missing") and the destroy can be retried. Its run and state directories go with the record,
|
|
130
|
-
// emptied from inside first: what a dimension running as root left there (an agent worker's socket
|
|
131
|
-
// and log) the host user couldn't remove.
|
|
132
34
|
async function destroyDimension(dimensionName) {
|
|
133
35
|
assertValidName(dimensionName);
|
|
134
|
-
|
|
135
|
-
const existing = await docker.inspectContainer(name);
|
|
136
|
-
if (existing) {
|
|
137
|
-
assertOwnedContainer(existing, dimensionName);
|
|
138
|
-
if (!existing.State?.Running)
|
|
139
|
-
await docker.startContainer(name).catch(() => { });
|
|
140
|
-
// Best effort: if it fails, removing them from the host below says why.
|
|
141
|
-
await docker
|
|
142
|
-
.exec(name, { cmd: ["sh", "-c", 'find "$R" "$S" -mindepth 1 -delete'], user: "root", env: { R: RUN_PATH, S: STATE_PATH } })
|
|
143
|
-
.catch(() => { });
|
|
144
|
-
await docker.removeContainer(name);
|
|
145
|
-
}
|
|
146
|
-
await storage.removeRunDirectory(dimensionName);
|
|
147
|
-
await storage.removeStateDirectory(dimensionName);
|
|
148
|
-
await storage.removeDimension(dimensionName);
|
|
36
|
+
await destroyDimensionScript(host, dimensionName, options);
|
|
149
37
|
}
|
|
150
|
-
// Same container, so the user and anything installed outside the workspace survive; the workspace
|
|
151
|
-
// is a bind mount either way.
|
|
152
38
|
async function restartDimension(dimensionName) {
|
|
153
39
|
assertValidName(dimensionName);
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
}
|
|
157
|
-
const existing = await docker.inspectContainer(containerName(dimensionName));
|
|
158
|
-
if (!existing)
|
|
159
|
-
throw new DimensionNotFoundError(`dimension ${dimensionName} has no container; create it again`);
|
|
160
|
-
assertOwnedContainer(existing, dimensionName);
|
|
161
|
-
await docker.restartContainer(containerName(dimensionName));
|
|
40
|
+
await restartDimensionScript(host, dimensionName, options);
|
|
41
|
+
await portals.restorePortals({ dimensionName });
|
|
42
|
+
await startTransportScript(host, dimensionName, options, { restart: true });
|
|
162
43
|
}
|
|
163
|
-
|
|
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)));
|
|
164
50
|
async function updateDimension(dimensionName, limits) {
|
|
165
51
|
assertValidName(dimensionName);
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
throw new DimensionInvalidError(`${name} must be positive`);
|
|
169
|
-
}
|
|
170
|
-
const record = (await storage.getDimensions()).find((dimension) => dimension.name === dimensionName);
|
|
171
|
-
if (!record)
|
|
172
|
-
throw new DimensionNotFoundError(`dimension ${dimensionName} does not exist`);
|
|
173
|
-
const existing = await docker.inspectContainer(containerName(dimensionName));
|
|
174
|
-
if (!existing)
|
|
175
|
-
throw new DimensionNotFoundError(`dimension ${dimensionName} has no container; create it again`);
|
|
176
|
-
assertOwnedContainer(existing, dimensionName);
|
|
177
|
-
const { state, ...current } = record;
|
|
178
|
-
const updated = {
|
|
179
|
-
...current,
|
|
180
|
-
memoryLimit: limits.memoryLimit ?? record.memoryLimit,
|
|
181
|
-
cpuLimit: limits.cpuLimit ?? record.cpuLimit,
|
|
182
|
-
};
|
|
183
|
-
await docker.updateContainer(containerName(dimensionName), containerResources(updated));
|
|
184
|
-
await storage.saveDimension({ ...updated, state });
|
|
52
|
+
assertLimits(limits);
|
|
53
|
+
const { state: _state, ...updated } = await updateDimensionScript(host, dimensionName, limits, options);
|
|
185
54
|
return updated;
|
|
186
55
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
throw new DimensionInvalidError(`directory ${directory} doesn't exist and can't be made: ${cause.message}`);
|
|
204
|
-
});
|
|
205
|
-
return host.stat(directory);
|
|
206
|
-
});
|
|
207
|
-
if (info.type !== "directory")
|
|
208
|
-
throw new DimensionInvalidError(`not a directory: ${directory}`);
|
|
209
|
-
return { uid: info.uid, gid: info.gid };
|
|
210
|
-
}
|
|
211
|
-
async function getDimensionNames() {
|
|
212
|
-
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`);
|
|
213
72
|
}
|
|
214
|
-
return { getDimensions, getDimensionNames, createDimension, destroyDimension, restartDimension, updateDimension };
|
|
215
73
|
}
|
|
216
74
|
function assertValidName(dimensionName) {
|
|
217
|
-
if (!
|
|
75
|
+
if (!isDimensionName(dimensionName)) {
|
|
218
76
|
throw new DimensionInvalidError(`invalid dimension name "${dimensionName}": use letters, digits, "_", "." or "-", starting with a letter or digit`);
|
|
219
77
|
}
|
|
220
78
|
}
|
|
221
|
-
function assertSameSpec(existing, requested) {
|
|
222
|
-
const differs = existing.directory !== requested.directory ||
|
|
223
|
-
existing.memoryLimit !== requested.memoryLimit ||
|
|
224
|
-
existing.cpuLimit !== requested.cpuLimit ||
|
|
225
|
-
[...(existing.portals ?? [])].sort().join("\0") !== [...(requested.portals ?? [])].sort().join("\0");
|
|
226
|
-
if (differs) {
|
|
227
|
-
throw new DimensionConflictError(`dimension ${requested.name} already exists with a different spec`);
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
function assertOwnedContainer(container, dimensionName) {
|
|
231
|
-
if (container.Config?.Labels?.["hyperbrane.dimension"] !== dimensionName) {
|
|
232
|
-
throw new DimensionConflictError(`container ${containerName(dimensionName)} exists but is not managed by hyperbrane`);
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
// Swap stays at twice the memory, which is what docker gives a container created with only a
|
|
236
|
-
// memory limit; setting both keeps an update valid whichever way the limit moves.
|
|
237
|
-
function containerResources({ memoryLimit, cpuLimit }) {
|
|
238
|
-
return { Memory: memoryLimit, MemorySwap: memoryLimit * 2, NanoCpus: Math.round(cpuLimit * 1e9) };
|
|
239
|
-
}
|
|
240
|
-
function withoutUsage(dimension, status) {
|
|
241
|
-
return { ...dimension, status, uptimeSeconds: null, memoryUsage: null, cpuUsage: null };
|
|
242
|
-
}
|
|
@@ -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;
|