@claxedo/sandbox-manager 0.7.0 → 0.10.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 +245 -42
- package/dist/checkpoint-manager.d.ts +29 -4
- package/dist/checkpoint-manager.mjs +133 -24
- package/dist/command.d.ts +9 -2
- package/dist/command.mjs +8 -8
- package/dist/contract.d.ts +687 -0
- package/dist/driver-billing.d.ts +29 -0
- package/dist/driver-catalog.d.ts +43 -58
- package/dist/driver-catalog.mjs +192 -229
- package/dist/drivers/boat-client.d.ts +95 -0
- package/dist/drivers/boat-named-snapshots.d.ts +31 -0
- package/dist/drivers/boat-template.d.ts +49 -0
- package/dist/drivers/boat-template.mjs +1196 -0
- package/dist/drivers/boat.d.ts +64 -0
- package/dist/drivers/boat.mjs +990 -0
- package/dist/drivers/cloudflare.d.ts +5 -8
- package/dist/drivers/cloudflare.mjs +364 -213
- package/dist/drivers/docker.d.ts +4 -3
- package/dist/drivers/docker.mjs +244 -165
- package/dist/drivers/local-brokering.d.ts +14 -0
- package/dist/drivers/local-brokering.mjs +1029 -0
- package/dist/drivers/modal.d.ts +4 -7
- package/dist/drivers/modal.mjs +220 -155
- package/dist/drivers/transient-error.d.ts +13 -0
- package/dist/drivers/vercel.d.ts +21 -23
- package/dist/drivers/vercel.mjs +279 -169
- package/dist/egress-policy.d.ts +134 -0
- package/dist/egress-warnings.d.ts +22 -0
- package/dist/garbage-collect.d.ts +14 -0
- package/dist/hosted-network-policy.d.ts +11 -4
- package/dist/hosted-network-policy.mjs +221 -4
- package/dist/image-name.d.ts +12 -2
- package/dist/image-name.mjs +43 -0
- package/dist/image.d.ts +1 -20
- package/dist/image.mjs +11 -66
- package/dist/index.d.ts +8 -533
- package/dist/index.mjs +1420 -315
- package/dist/json.d.ts +13 -0
- package/dist/lease-failure.d.ts +173 -0
- package/dist/lease-lifecycle.d.ts +65 -0
- package/dist/lease-retry-wait.d.ts +8 -0
- package/dist/lease-start.d.ts +21 -0
- package/dist/lease-types.d.ts +15 -5
- package/dist/manager.d.ts +2 -0
- package/dist/runtime-env.d.ts +15 -2
- package/dist/runtime-env.mjs +50 -1
- package/dist/runtime-snapshot.d.ts +49 -0
- package/dist/runtime-version.d.ts +9 -0
- package/dist/runtime-version.mjs +2 -0
- package/dist/service-url-exposure.d.ts +1 -3
- package/dist/service-url-exposure.mjs +1 -26
- package/dist/start-fresh.d.ts +17 -0
- package/dist/start-telemetry.d.ts +37 -0
- package/dist/stores/memory.d.ts +1 -1
- package/dist/stores/memory.mjs +65 -54
- package/package.json +67 -81
- package/dist/daytona-allow-list.d.ts +0 -16
- package/dist/daytona-allow-list.mjs +0 -11
- package/dist/drivers/box.d.ts +0 -52
- package/dist/drivers/box.mjs +0 -499
- package/dist/drivers/cloudflare-egress.d.ts +0 -40
- package/dist/drivers/cloudflare-egress.mjs +0 -113
- package/dist/drivers/daytona.d.ts +0 -127
- package/dist/drivers/daytona.mjs +0 -541
- package/dist/drivers/exe.d.ts +0 -26
- package/dist/drivers/exe.mjs +0 -469
- package/dist/drivers/fetch-bridge.d.ts +0 -10
- package/dist/drivers/fetch-bridge.mjs +0 -111
- package/dist/lease-policy.d.ts +0 -48
- package/dist/lease-policy.mjs +0 -280
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
import type { SandboxDriver, SandboxDriverEnsureInput } from "..";
|
|
2
|
-
export type DaytonaSandboxLike = {
|
|
3
|
-
id: string;
|
|
4
|
-
state?: string;
|
|
5
|
-
labels?: Record<string, string>;
|
|
6
|
-
process: {
|
|
7
|
-
executeCommand: (command: string, cwd?: string, env?: Record<string, string>, timeout?: number) => Promise<unknown>;
|
|
8
|
-
};
|
|
9
|
-
getPreviewLink: (port: number) => Promise<{
|
|
10
|
-
url?: string;
|
|
11
|
-
token?: string;
|
|
12
|
-
}>;
|
|
13
|
-
getSignedPreviewUrl: (port: number, expiresInSeconds?: number) => Promise<{
|
|
14
|
-
url?: string;
|
|
15
|
-
token?: string;
|
|
16
|
-
}>;
|
|
17
|
-
refreshActivity: () => Promise<void>;
|
|
18
|
-
start: (timeout?: number) => Promise<void>;
|
|
19
|
-
stop: (timeout?: number) => Promise<void>;
|
|
20
|
-
delete: (timeout?: number) => Promise<void>;
|
|
21
|
-
/**
|
|
22
|
-
* Replace the sandbox's outbound egress policy in place, without stopping it
|
|
23
|
-
* — the Daytona SDK's `Sandbox.updateNetworkSettings`, which drives the same
|
|
24
|
-
* runner-side iptables mechanism as `create`'s `networkBlockAll` /
|
|
25
|
-
* `networkAllowList` / `domainAllowList`. This is what makes the create-time
|
|
26
|
-
* policy reappliable on reuse and resume; see `applyNetworkPolicy` below.
|
|
27
|
-
*
|
|
28
|
-
* Optional because a client injected by an embedder may predate it. When it
|
|
29
|
-
* is absent and a restricted policy was requested, reuse is REFUSED rather
|
|
30
|
-
* than silently served on the creation-time policy.
|
|
31
|
-
*/
|
|
32
|
-
updateNetworkSettings?: (settings: {
|
|
33
|
-
networkBlockAll?: boolean;
|
|
34
|
-
networkAllowList?: string;
|
|
35
|
-
domainAllowList?: string;
|
|
36
|
-
}) => Promise<void>;
|
|
37
|
-
_experimental_createSnapshot?: (name: string, timeout?: number) => Promise<void>;
|
|
38
|
-
};
|
|
39
|
-
export type DaytonaClientLike = {
|
|
40
|
-
findByLabels?: (labels: Record<string, string>) => Promise<DaytonaSandboxLike | undefined>;
|
|
41
|
-
/**
|
|
42
|
-
* One page of sandboxes matching `labels`. Pages are 1-based; a page that
|
|
43
|
-
* returns fewer than `limit` items (or none) is the last one — that is the
|
|
44
|
-
* termination rule `list()` below relies on, and any implementation must
|
|
45
|
-
* honor it or the sweep loops forever.
|
|
46
|
-
*
|
|
47
|
-
* `createDefaultClient` satisfies it by returning every match on page 1 and
|
|
48
|
-
* nothing after: the Daytona SDK's `list()` is an `AsyncIterableIterator`
|
|
49
|
-
* that paginates internally, so `limit` is its per-page fetch size and NOT a
|
|
50
|
-
* cap on the total (see @daytona/sdk `ListSandboxesQuery.limit`). There is no
|
|
51
|
-
* cursor to hand back, so re-slicing per page would re-walk the whole account
|
|
52
|
-
* on every call.
|
|
53
|
-
*/
|
|
54
|
-
list?: (labels?: Record<string, string>, page?: number, limit?: number) => Promise<{
|
|
55
|
-
items?: DaytonaSandboxLike[];
|
|
56
|
-
}>;
|
|
57
|
-
create: (params: {
|
|
58
|
-
name?: string;
|
|
59
|
-
image?: string;
|
|
60
|
-
snapshot?: string;
|
|
61
|
-
envVars?: Record<string, string>;
|
|
62
|
-
/** Env-var-name → Daytona secret-name references (brokered secrets). */
|
|
63
|
-
secrets?: Record<string, string>;
|
|
64
|
-
labels?: Record<string, string>;
|
|
65
|
-
public?: boolean;
|
|
66
|
-
autoStopInterval?: number;
|
|
67
|
-
autoDeleteInterval?: number;
|
|
68
|
-
networkBlockAll?: boolean;
|
|
69
|
-
/** Comma-separated allowed CIDR network addresses. */
|
|
70
|
-
networkAllowList?: string;
|
|
71
|
-
/** Comma-separated allowed domains — the name-based egress allowlist. */
|
|
72
|
-
domainAllowList?: string;
|
|
73
|
-
}, options?: {
|
|
74
|
-
timeout?: number;
|
|
75
|
-
}) => Promise<DaytonaSandboxLike>;
|
|
76
|
-
get: (sandboxIdOrName: string) => Promise<DaytonaSandboxLike>;
|
|
77
|
-
/**
|
|
78
|
-
* Create-or-update an org secret whose value is only substituted by
|
|
79
|
-
* Daytona's egress proxy for requests to `hosts`
|
|
80
|
-
* (https://www.daytona.io/docs/en/secrets/). Referenced from `create`'s
|
|
81
|
-
* `secrets` map; inside the sandbox the env var holds only an opaque
|
|
82
|
-
* `dtn_secret_…` placeholder, never the value.
|
|
83
|
-
*/
|
|
84
|
-
upsertSecret?: (params: {
|
|
85
|
-
name: string;
|
|
86
|
-
value: string;
|
|
87
|
-
hosts: string[];
|
|
88
|
-
}) => Promise<void>;
|
|
89
|
-
};
|
|
90
|
-
export type DaytonaSandboxDriverOptions = {
|
|
91
|
-
/** Daytona API base URL. Defaults to the SDK default. */
|
|
92
|
-
apiUrl?: string;
|
|
93
|
-
/** Daytona API key. */
|
|
94
|
-
apiKey: string;
|
|
95
|
-
/** Optional Daytona organization id. */
|
|
96
|
-
organizationId?: string;
|
|
97
|
-
/** Optional Daytona target/region. */
|
|
98
|
-
target?: string;
|
|
99
|
-
/** Snapshot the sandbox boots from. */
|
|
100
|
-
baseSnapshot: string;
|
|
101
|
-
/** Port the workspace-runtime listens on inside the sandbox. */
|
|
102
|
-
runtimePort?: number;
|
|
103
|
-
/** Path to the workspace-runtime binary inside the image. */
|
|
104
|
-
runtimeCommand?: string;
|
|
105
|
-
/** Workspace directory inside the sandbox. */
|
|
106
|
-
workspaceDir?: string;
|
|
107
|
-
/** Default runner injected as WORKSPACE_RUNTIME_RUNNER. */
|
|
108
|
-
runner?: string;
|
|
109
|
-
/** Static control-plane config injected so the runtime verifies relay-proxied requests. */
|
|
110
|
-
controlEnv?: {
|
|
111
|
-
relayJwksUrl?: string;
|
|
112
|
-
relayVerifyPem?: string;
|
|
113
|
-
managementJwksUrl?: string;
|
|
114
|
-
};
|
|
115
|
-
/** Dynamic runtime env that needs the acquired sandbox id or current lease. */
|
|
116
|
-
env?: (input: SandboxDriverEnsureInput, sandbox: DaytonaSandboxLike) => Record<string, string> | Promise<Record<string, string>>;
|
|
117
|
-
/** Auto-stop / auto-delete policy, in minutes. */
|
|
118
|
-
autoStopMinutes?: number;
|
|
119
|
-
autoDeleteMinutes?: number;
|
|
120
|
-
/** Signed preview URL expiry, in seconds. */
|
|
121
|
-
previewExpirySeconds?: number;
|
|
122
|
-
/** Daytona SDK operation timeout, in seconds. */
|
|
123
|
-
operationTimeoutSeconds?: number;
|
|
124
|
-
/** Injected for tests. */
|
|
125
|
-
client?: DaytonaClientLike;
|
|
126
|
-
};
|
|
127
|
-
export declare function createDaytonaSandboxDriver(options: DaytonaSandboxDriverOptions): SandboxDriver;
|
package/dist/drivers/daytona.mjs
DELETED
|
@@ -1,541 +0,0 @@
|
|
|
1
|
-
// src/drivers/daytona.ts
|
|
2
|
-
import { Daytona } from "@daytona/sdk";
|
|
3
|
-
|
|
4
|
-
// src/daytona-allow-list.ts
|
|
5
|
-
function formatDaytonaAllowList(cidrs) {
|
|
6
|
-
return cidrs.slice(0, 10).join(",");
|
|
7
|
-
}
|
|
8
|
-
function formatDaytonaDomainAllowList(hosts) {
|
|
9
|
-
return [...new Set(hosts)].join(",");
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
// src/runtime-env.ts
|
|
13
|
-
function workspaceRuntimeTargetEnv(input) {
|
|
14
|
-
return {
|
|
15
|
-
WORKSPACE_RUNTIME_WORKSPACE_ID: input.workspaceId,
|
|
16
|
-
...input.hostId ? { WORKSPACE_RUNTIME_HOST_ID: input.hostId } : {},
|
|
17
|
-
WORKSPACE_RUNTIME_DIRECTORY: input.directory,
|
|
18
|
-
WORKSPACE_RUNTIME_PORT: String(input.port),
|
|
19
|
-
...input.host ? { WORKSPACE_RUNTIME_HOST: input.host } : {}
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
function workspaceRuntimeSourceEnv(input) {
|
|
23
|
-
if (!input.source || input.source.kind === "empty") return { WORKSPACE_RUNTIME_SOURCE_KIND: "empty" };
|
|
24
|
-
return {
|
|
25
|
-
WORKSPACE_RUNTIME_SOURCE_KIND: "git",
|
|
26
|
-
WORKSPACE_RUNTIME_GIT_REPO_URL: input.source.repoUrl,
|
|
27
|
-
...input.source.branch ? { WORKSPACE_RUNTIME_GIT_BRANCH: input.source.branch } : {}
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// src/command.ts
|
|
32
|
-
function shell(input) {
|
|
33
|
-
return `'${input.replace(/'/g, `'"'"'`)}'`;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// src/constants.ts
|
|
37
|
-
var DEFAULT_WORKSPACE_RUNTIME_PORT = 2593;
|
|
38
|
-
|
|
39
|
-
// src/hosted-network-policy.ts
|
|
40
|
-
var SANDBOX_MODEL_PROVIDER_HOSTS = [
|
|
41
|
-
"api.anthropic.com",
|
|
42
|
-
"api.openai.com",
|
|
43
|
-
"generativelanguage.googleapis.com",
|
|
44
|
-
"openrouter.ai",
|
|
45
|
-
// Model catalog + engine config, fetched on runner startup.
|
|
46
|
-
"models.dev",
|
|
47
|
-
"opencode.ai"
|
|
48
|
-
];
|
|
49
|
-
var SANDBOX_PACKAGE_REGISTRY_HOSTS = [
|
|
50
|
-
"registry.npmjs.org",
|
|
51
|
-
"nodejs.org",
|
|
52
|
-
"pypi.org",
|
|
53
|
-
"files.pythonhosted.org",
|
|
54
|
-
"crates.io",
|
|
55
|
-
"static.crates.io",
|
|
56
|
-
"index.crates.io",
|
|
57
|
-
"proxy.golang.org",
|
|
58
|
-
"sum.golang.org"
|
|
59
|
-
];
|
|
60
|
-
var SANDBOX_BASELINE_EGRESS_HOSTS = [
|
|
61
|
-
...SANDBOX_MODEL_PROVIDER_HOSTS,
|
|
62
|
-
...SANDBOX_PACKAGE_REGISTRY_HOSTS
|
|
63
|
-
];
|
|
64
|
-
|
|
65
|
-
// src/driver-catalog.ts
|
|
66
|
-
var sandboxDriverCatalog = {
|
|
67
|
-
exe: {
|
|
68
|
-
id: "exe",
|
|
69
|
-
label: "exe.dev",
|
|
70
|
-
credentialFields: [{ key: "api_token", label: "API Token", secret: true }],
|
|
71
|
-
metadata: {
|
|
72
|
-
driverRunsIn: ["worker", "node"],
|
|
73
|
-
hostStopBehavior: "not-supported",
|
|
74
|
-
hostResumeBehavior: "same-host",
|
|
75
|
-
targetAccess: "relay",
|
|
76
|
-
secretBrokering: "none",
|
|
77
|
-
egressControl: "none",
|
|
78
|
-
persistence: {
|
|
79
|
-
resume: "same-sandbox",
|
|
80
|
-
capture: "none",
|
|
81
|
-
clone: true,
|
|
82
|
-
captureSource: "not-applicable",
|
|
83
|
-
retention: "provider-managed",
|
|
84
|
-
restoreMount: "same-resource"
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
},
|
|
88
|
-
daytona: {
|
|
89
|
-
id: "daytona",
|
|
90
|
-
label: "Daytona",
|
|
91
|
-
credentialFields: [{ key: "api_key", label: "API Key", secret: true }],
|
|
92
|
-
metadata: {
|
|
93
|
-
driverRunsIn: ["worker", "node"],
|
|
94
|
-
hostStopBehavior: "suspends-host",
|
|
95
|
-
hostResumeBehavior: "same-host",
|
|
96
|
-
targetAccess: "relay",
|
|
97
|
-
secretBrokering: "native",
|
|
98
|
-
// Daytona is the only driver that can filter egress by NAME as well as
|
|
99
|
-
// by address: `domainAllowList` (names) alongside `networkAllowList`
|
|
100
|
-
// (CIDRs), with `networkBlockAll` as the deny-all floor.
|
|
101
|
-
egressControl: "hosts-and-cidrs",
|
|
102
|
-
persistence: {
|
|
103
|
-
resume: "same-sandbox",
|
|
104
|
-
capture: "filesystem",
|
|
105
|
-
clone: false,
|
|
106
|
-
captureSource: "preserved",
|
|
107
|
-
retention: "provider-managed",
|
|
108
|
-
restoreMount: "new-resource"
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
},
|
|
112
|
-
modal: {
|
|
113
|
-
id: "modal",
|
|
114
|
-
label: "Modal",
|
|
115
|
-
credentialFields: [
|
|
116
|
-
{ key: "token_id", label: "Token ID", secret: true },
|
|
117
|
-
{ key: "token_secret", label: "Token Secret", secret: true }
|
|
118
|
-
],
|
|
119
|
-
metadata: {
|
|
120
|
-
driverRunsIn: ["node"],
|
|
121
|
-
hostStopBehavior: "terminates-host",
|
|
122
|
-
hostResumeBehavior: "replacement-host",
|
|
123
|
-
targetAccess: "relay",
|
|
124
|
-
secretBrokering: "none",
|
|
125
|
-
// Modal can cut the network entirely (`blockNetwork`) but cannot express
|
|
126
|
-
// an allowlist, and its driver throws on a host policy. A blackout is
|
|
127
|
-
// not containment for a workspace that has to clone and reach a model.
|
|
128
|
-
egressControl: "none",
|
|
129
|
-
persistence: {
|
|
130
|
-
resume: "replacement-restore",
|
|
131
|
-
capture: "filesystem",
|
|
132
|
-
clone: false,
|
|
133
|
-
captureSource: "preserved",
|
|
134
|
-
retention: "provider-managed",
|
|
135
|
-
restoreMount: "new-resource"
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
},
|
|
139
|
-
vercel: {
|
|
140
|
-
id: "vercel",
|
|
141
|
-
label: "Vercel",
|
|
142
|
-
credentialFields: [
|
|
143
|
-
{ key: "access_token", label: "Access Token", secret: true },
|
|
144
|
-
{ key: "team_id", label: "Team ID" },
|
|
145
|
-
{ key: "project_id", label: "Project ID" }
|
|
146
|
-
],
|
|
147
|
-
metadata: {
|
|
148
|
-
driverRunsIn: ["node"],
|
|
149
|
-
hostStopBehavior: "terminates-host",
|
|
150
|
-
hostResumeBehavior: "replacement-host",
|
|
151
|
-
targetAccess: "relay",
|
|
152
|
-
secretBrokering: "native",
|
|
153
|
-
// Vercel's sandbox firewall takes a hostname allow list (or "deny-all"),
|
|
154
|
-
// never CIDRs.
|
|
155
|
-
egressControl: "hosts",
|
|
156
|
-
persistence: {
|
|
157
|
-
resume: "replacement-restore",
|
|
158
|
-
capture: "filesystem",
|
|
159
|
-
clone: false,
|
|
160
|
-
captureSource: "stopped",
|
|
161
|
-
retention: "explicit",
|
|
162
|
-
restoreMount: "new-resource"
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
},
|
|
166
|
-
cloudflare: {
|
|
167
|
-
id: "cloudflare",
|
|
168
|
-
label: "Cloudflare",
|
|
169
|
-
credentialFields: [
|
|
170
|
-
{ key: "api_token", label: "API Token", secret: true },
|
|
171
|
-
{ key: "worker_url", label: "Worker URL" }
|
|
172
|
-
],
|
|
173
|
-
metadata: {
|
|
174
|
-
driverRunsIn: ["worker"],
|
|
175
|
-
hostStopBehavior: "not-supported",
|
|
176
|
-
hostResumeBehavior: "same-host",
|
|
177
|
-
targetAccess: "relay",
|
|
178
|
-
secretBrokering: "proxy",
|
|
179
|
-
// The Cloudflare Sandbox Worker exposes no egress filter: its broker
|
|
180
|
-
// (drivers/cloudflare-egress.ts) is an OPT-IN proxy the sandbox chooses
|
|
181
|
-
// to route brokered-credential requests through, not a boundary. The
|
|
182
|
-
// driver therefore drops `net` entirely — which is exactly why this
|
|
183
|
-
// must be declared, so the manager refuses instead of provisioning a
|
|
184
|
-
// sandbox the caller believes is contained.
|
|
185
|
-
egressControl: "none",
|
|
186
|
-
persistence: {
|
|
187
|
-
resume: "replacement-restore",
|
|
188
|
-
capture: "directories",
|
|
189
|
-
clone: false,
|
|
190
|
-
captureSource: "preserved",
|
|
191
|
-
retention: "provider-managed",
|
|
192
|
-
restoreMount: "copy-on-write"
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
},
|
|
196
|
-
box: {
|
|
197
|
-
id: "box",
|
|
198
|
-
label: "Box",
|
|
199
|
-
credentialFields: [{ key: "api_key", label: "API Key", secret: true }],
|
|
200
|
-
metadata: {
|
|
201
|
-
driverRunsIn: ["node"],
|
|
202
|
-
hostStopBehavior: "suspends-host",
|
|
203
|
-
hostResumeBehavior: "same-host",
|
|
204
|
-
targetAccess: "relay",
|
|
205
|
-
secretBrokering: "none",
|
|
206
|
-
egressControl: "none",
|
|
207
|
-
persistence: {
|
|
208
|
-
resume: "same-sandbox",
|
|
209
|
-
capture: "none",
|
|
210
|
-
clone: false,
|
|
211
|
-
captureSource: "not-applicable",
|
|
212
|
-
retention: "provider-managed",
|
|
213
|
-
restoreMount: "same-resource"
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
},
|
|
217
|
-
docker: {
|
|
218
|
-
id: "docker",
|
|
219
|
-
label: "Docker",
|
|
220
|
-
credentialFields: [{ key: "image", label: "Image" }],
|
|
221
|
-
metadata: {
|
|
222
|
-
driverRunsIn: ["local"],
|
|
223
|
-
hostStopBehavior: "terminates-host",
|
|
224
|
-
hostResumeBehavior: "same-host",
|
|
225
|
-
targetAccess: "loopback",
|
|
226
|
-
secretBrokering: "none",
|
|
227
|
-
egressControl: "none",
|
|
228
|
-
persistence: {
|
|
229
|
-
resume: "replacement-restore",
|
|
230
|
-
capture: "same-resource",
|
|
231
|
-
clone: false,
|
|
232
|
-
captureSource: "preserved",
|
|
233
|
-
retention: "provider-managed",
|
|
234
|
-
restoreMount: "same-resource"
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
};
|
|
239
|
-
|
|
240
|
-
// src/drivers/daytona.ts
|
|
241
|
-
var DEFAULT_RUNTIME_COMMAND = "/usr/local/bin/workspace-runtime";
|
|
242
|
-
var DEFAULT_WORKSPACE_DIR = "/workspace";
|
|
243
|
-
var DEFAULT_PREVIEW_EXPIRY_S = 3600;
|
|
244
|
-
var DEFAULT_OPERATION_TIMEOUT_S = 60;
|
|
245
|
-
var LIST_PAGE_SIZE = 100;
|
|
246
|
-
var MAX_LIST_PAGES = 100;
|
|
247
|
-
function labelName(workspaceId) {
|
|
248
|
-
return `claxedo-${workspaceId}`;
|
|
249
|
-
}
|
|
250
|
-
function daytonaSecretName(workspaceId, secretName) {
|
|
251
|
-
const safe = (value) => value.replace(/[^a-zA-Z0-9_-]+/g, "-");
|
|
252
|
-
return `claxedo-${safe(workspaceId)}-${safe(secretName)}`;
|
|
253
|
-
}
|
|
254
|
-
function transientDriverError(err) {
|
|
255
|
-
const shaped = err;
|
|
256
|
-
const status = shaped.response?.status ?? shaped.status;
|
|
257
|
-
if (typeof status === "number" && status >= 500) return true;
|
|
258
|
-
const text = `${shaped.name ?? ""} ${shaped.message ?? ""}`.toLowerCase();
|
|
259
|
-
return text.includes("timeout") || text.includes("pending") || text.includes("starting");
|
|
260
|
-
}
|
|
261
|
-
function createDefaultClient(options) {
|
|
262
|
-
const sdk = new Daytona({
|
|
263
|
-
apiKey: options.apiKey,
|
|
264
|
-
...options.apiUrl ? { apiUrl: options.apiUrl } : {},
|
|
265
|
-
...options.organizationId ? { organizationId: options.organizationId } : {},
|
|
266
|
-
...options.target ? { target: options.target } : {},
|
|
267
|
-
_experimental: { otelEnabled: false }
|
|
268
|
-
});
|
|
269
|
-
return {
|
|
270
|
-
async findByLabels(labels) {
|
|
271
|
-
for await (const sandbox of sdk.list({ labels })) return sandbox;
|
|
272
|
-
return void 0;
|
|
273
|
-
},
|
|
274
|
-
// Page 1 carries everything; later pages are empty. See DaytonaClientLike.list.
|
|
275
|
-
async list(labels, page = 1, limit) {
|
|
276
|
-
if (page > 1) return { items: [] };
|
|
277
|
-
const items = [];
|
|
278
|
-
for await (const sandbox of sdk.list({ ...labels ? { labels } : {}, ...limit ? { limit } : {} })) {
|
|
279
|
-
items.push(sandbox);
|
|
280
|
-
}
|
|
281
|
-
return { items };
|
|
282
|
-
},
|
|
283
|
-
create(params, operation) {
|
|
284
|
-
if (params.image) return sdk.create({ ...params, image: params.image }, operation);
|
|
285
|
-
return sdk.create(params, operation);
|
|
286
|
-
},
|
|
287
|
-
get: (sandboxIdOrName) => sdk.get(sandboxIdOrName),
|
|
288
|
-
async upsertSecret({ name, value, hosts }) {
|
|
289
|
-
await sdk.secret.create({ name, value, hosts });
|
|
290
|
-
}
|
|
291
|
-
};
|
|
292
|
-
}
|
|
293
|
-
function createDaytonaSandboxDriver(options) {
|
|
294
|
-
const client = options.client ?? createDefaultClient(options);
|
|
295
|
-
const runtimePort = options.runtimePort ?? DEFAULT_WORKSPACE_RUNTIME_PORT;
|
|
296
|
-
const runtimeCommand = options.runtimeCommand ?? DEFAULT_RUNTIME_COMMAND;
|
|
297
|
-
const workspaceDir = options.workspaceDir ?? DEFAULT_WORKSPACE_DIR;
|
|
298
|
-
const previewExpiry = options.previewExpirySeconds ?? DEFAULT_PREVIEW_EXPIRY_S;
|
|
299
|
-
const operationTimeout = options.operationTimeoutSeconds ?? DEFAULT_OPERATION_TIMEOUT_S;
|
|
300
|
-
function workspaceDirectory(input) {
|
|
301
|
-
return input.workspaceRoot ?? workspaceDir;
|
|
302
|
-
}
|
|
303
|
-
function staticBootEnv(input, hostId, directory) {
|
|
304
|
-
const env = {
|
|
305
|
-
...workspaceRuntimeTargetEnv({
|
|
306
|
-
workspaceId: input.workspaceId,
|
|
307
|
-
hostId,
|
|
308
|
-
directory,
|
|
309
|
-
port: runtimePort
|
|
310
|
-
}),
|
|
311
|
-
...workspaceRuntimeSourceEnv({ source: input.source }),
|
|
312
|
-
...input.env
|
|
313
|
-
};
|
|
314
|
-
if (options.runner) env.WORKSPACE_RUNTIME_RUNNER = options.runner;
|
|
315
|
-
if (options.controlEnv?.relayJwksUrl) env.WORKSPACE_RUNTIME_RELAY_JWKS_URL = options.controlEnv.relayJwksUrl;
|
|
316
|
-
if (options.controlEnv?.relayVerifyPem) env.WORKSPACE_RUNTIME_RELAY_HOST_VERIFY_PEM = options.controlEnv.relayVerifyPem;
|
|
317
|
-
if (options.controlEnv?.managementJwksUrl) env.WORKSPACE_RUNTIME_MANAGEMENT_JWKS_URL = options.controlEnv.managementJwksUrl;
|
|
318
|
-
return env;
|
|
319
|
-
}
|
|
320
|
-
async function bootEnv(input, sandbox, hostId) {
|
|
321
|
-
return {
|
|
322
|
-
...staticBootEnv(input, hostId, workspaceDirectory(input)),
|
|
323
|
-
...await options.env?.(input, sandbox)
|
|
324
|
-
};
|
|
325
|
-
}
|
|
326
|
-
async function findExisting(workspaceId) {
|
|
327
|
-
const labels = { "claxedo.workspaceId": workspaceId };
|
|
328
|
-
if (client.findByLabels) return client.findByLabels(labels).catch(() => void 0);
|
|
329
|
-
const result = await client.list?.(labels, 1, 1).catch(() => void 0);
|
|
330
|
-
return result?.items?.[0];
|
|
331
|
-
}
|
|
332
|
-
async function sandboxById(sandboxId) {
|
|
333
|
-
return client.get(sandboxId);
|
|
334
|
-
}
|
|
335
|
-
async function previewUrl(sandbox) {
|
|
336
|
-
const signed = await sandbox.getSignedPreviewUrl(runtimePort, previewExpiry).catch(() => void 0);
|
|
337
|
-
if (signed?.url) return signed;
|
|
338
|
-
return sandbox.getPreviewLink(runtimePort).catch(() => void 0);
|
|
339
|
-
}
|
|
340
|
-
async function startRuntime(sandbox, input, env) {
|
|
341
|
-
const directory = workspaceDirectory(input);
|
|
342
|
-
const script = `if (command -v ss >/dev/null && ss -ltn 2>/dev/null | grep -q :${runtimePort}); then exit 0; fi; mkdir -p ${shell(directory)}; cd ${shell(directory)}; nohup ${runtimeCommand} > /tmp/claxedo-wr.log 2>&1 & sleep 1`;
|
|
343
|
-
await sandbox.process.executeCommand(`sh -lc ${shell(script)}`, directory, env, operationTimeout).catch(() => void 0);
|
|
344
|
-
}
|
|
345
|
-
async function ensureStarted(sandbox) {
|
|
346
|
-
if (!sandbox.state || sandbox.state === "started") return true;
|
|
347
|
-
if (sandbox.state === "starting") return false;
|
|
348
|
-
await sandbox.start(operationTimeout).catch(() => void 0);
|
|
349
|
-
return sandbox.state === "started";
|
|
350
|
-
}
|
|
351
|
-
async function readyTarget(input, sandbox, hostId) {
|
|
352
|
-
await startRuntime(sandbox, input, await bootEnv(input, sandbox, hostId));
|
|
353
|
-
const preview = await previewUrl(sandbox);
|
|
354
|
-
if (!preview?.url) return { provisioning: true, retryAfterMs: 2e3 };
|
|
355
|
-
const target = {
|
|
356
|
-
workspaceId: input.workspaceId,
|
|
357
|
-
sandboxId: sandbox.id,
|
|
358
|
-
url: preview.url,
|
|
359
|
-
hostId,
|
|
360
|
-
driverResourceId: sandbox.id,
|
|
361
|
-
driver: {
|
|
362
|
-
id: "daytona",
|
|
363
|
-
resourceId: sandbox.id
|
|
364
|
-
},
|
|
365
|
-
labels: { ...input.labels, ...preview.token ? { "daytona.previewToken": preview.token } : {} }
|
|
366
|
-
};
|
|
367
|
-
return target;
|
|
368
|
-
}
|
|
369
|
-
async function brokeredSecretReferences(input) {
|
|
370
|
-
if (!input.secrets?.length) return {};
|
|
371
|
-
if (!client.upsertSecret) {
|
|
372
|
-
throw new Error("daytona sandbox client does not support secret brokering");
|
|
373
|
-
}
|
|
374
|
-
const references = {};
|
|
375
|
-
for (const secret of input.secrets) {
|
|
376
|
-
if (secret.hosts.length === 0) {
|
|
377
|
-
throw new Error(`daytona brokered secret "${secret.name}" requires at least one host in its egress allowlist`);
|
|
378
|
-
}
|
|
379
|
-
const secretName = daytonaSecretName(input.workspaceId, secret.name);
|
|
380
|
-
await client.upsertSecret({ name: secretName, value: secret.value, hosts: secret.hosts });
|
|
381
|
-
references[secret.name] = secretName;
|
|
382
|
-
}
|
|
383
|
-
return references;
|
|
384
|
-
}
|
|
385
|
-
async function applyNetworkPolicy(sandbox, input) {
|
|
386
|
-
const net = network(input);
|
|
387
|
-
if (Object.keys(net).length === 0) return "applied";
|
|
388
|
-
if (!sandbox.updateNetworkSettings) {
|
|
389
|
-
throw new Error(
|
|
390
|
-
`daytona sandbox client cannot reapply egress policy to existing sandbox ${sandbox.id}: the client does not support updateNetworkSettings, so the requested restricted policy cannot be guaranteed on reuse \u2014 refusing rather than serving the creation-time policy`
|
|
391
|
-
);
|
|
392
|
-
}
|
|
393
|
-
try {
|
|
394
|
-
await sandbox.updateNetworkSettings(net);
|
|
395
|
-
} catch (err) {
|
|
396
|
-
if (transientDriverError(err)) return "retry";
|
|
397
|
-
throw err;
|
|
398
|
-
}
|
|
399
|
-
return "applied";
|
|
400
|
-
}
|
|
401
|
-
async function ensureHost(input) {
|
|
402
|
-
const hostId = labelName(input.workspaceId);
|
|
403
|
-
const bootSource = input.bootSource?.kind === "image" ? { image: input.bootSource.image } : { snapshot: input.bootSource?.kind === "driver-snapshot" ? input.bootSource.snapshotId : input.snapshot ?? options.baseSnapshot };
|
|
404
|
-
const net = network(input);
|
|
405
|
-
const secrets = await brokeredSecretReferences(input);
|
|
406
|
-
const existing = await findExisting(input.workspaceId);
|
|
407
|
-
const sandbox = existing ?? await client.create({
|
|
408
|
-
name: labelName(input.workspaceId),
|
|
409
|
-
...bootSource,
|
|
410
|
-
envVars: staticBootEnv(input, hostId, workspaceDirectory(input)),
|
|
411
|
-
...Object.keys(secrets).length ? { secrets } : {},
|
|
412
|
-
labels: { ...input.labels, "claxedo.workspaceId": input.workspaceId },
|
|
413
|
-
public: false,
|
|
414
|
-
...net,
|
|
415
|
-
...options.autoStopMinutes !== void 0 ? { autoStopInterval: options.autoStopMinutes } : {},
|
|
416
|
-
...options.autoDeleteMinutes !== void 0 ? { autoDeleteInterval: options.autoDeleteMinutes } : {}
|
|
417
|
-
}, { timeout: operationTimeout }).catch((err) => {
|
|
418
|
-
if (transientDriverError(err)) return void 0;
|
|
419
|
-
throw err;
|
|
420
|
-
});
|
|
421
|
-
if (!sandbox) return { provisioning: true, retryAfterMs: 2e3 };
|
|
422
|
-
if (!await ensureStarted(sandbox)) return { provisioning: true, retryAfterMs: 2e3 };
|
|
423
|
-
if (existing && await applyNetworkPolicy(existing, input) === "retry") {
|
|
424
|
-
return { provisioning: true, retryAfterMs: 2e3 };
|
|
425
|
-
}
|
|
426
|
-
return readyTarget(input, sandbox, hostId);
|
|
427
|
-
}
|
|
428
|
-
return {
|
|
429
|
-
id: "daytona",
|
|
430
|
-
metadata: {
|
|
431
|
-
driverRunsIn: ["worker", "node"],
|
|
432
|
-
hostStopBehavior: "suspends-host",
|
|
433
|
-
hostResumeBehavior: "same-host",
|
|
434
|
-
targetAccess: "relay",
|
|
435
|
-
secretBrokering: "native",
|
|
436
|
-
egressControl: "hosts-and-cidrs",
|
|
437
|
-
persistence: sandboxDriverCatalog.daytona.metadata.persistence
|
|
438
|
-
},
|
|
439
|
-
ensureHost,
|
|
440
|
-
// Provider-state enumeration for `garbageCollect()`.
|
|
441
|
-
//
|
|
442
|
-
// Two label sets exist on a sandbox this driver created (`ensureHost`
|
|
443
|
-
// above): the manager's flat set — `app`, `workspaceId`, `epoch`,
|
|
444
|
-
// `homeRegion` (built at index.ts `ensureHostInput`) — and the dotted
|
|
445
|
-
// `claxedo.workspaceId` this driver adds for `findExisting`. Every target
|
|
446
|
-
// returned here carries `sandbox.labels` **as the provider reports them**,
|
|
447
|
-
// never labels reconstructed from ensure inputs: GC's ownership check reads
|
|
448
|
-
// `labels.app` and its identity check reads flat `labels.workspaceId` /
|
|
449
|
-
// `labels.epoch`, so a synthesized label set would decide the fate of a
|
|
450
|
-
// sandbox using values that never left this process. (That is the bug in
|
|
451
|
-
// the `exe` driver's `list()`, which sources labels from a per-process
|
|
452
|
-
// `Map` — after a restart it reports `labels: undefined` and GC skips
|
|
453
|
-
// every sandbox as unlabeled. Not fixed here; different owner.)
|
|
454
|
-
//
|
|
455
|
-
// The filter is deliberately WIDE: any claxedo ownership marker qualifies.
|
|
456
|
-
// Narrowing it here would re-create the defect W1 exists to remove — an
|
|
457
|
-
// orphan the sweep cannot see is an orphan that lives forever — and the
|
|
458
|
-
// authority on what may be DESTROYED is the manager's `app`-label check,
|
|
459
|
-
// which skips anything else as `unmanaged_app_label`. Visibility is this
|
|
460
|
-
// function's job; destruction is not.
|
|
461
|
-
//
|
|
462
|
-
// No preview URL is resolved: `getSignedPreviewUrl` is a per-sandbox
|
|
463
|
-
// round-trip and GC only needs identity, so a sweep over N orphans would
|
|
464
|
-
// cost N extra API calls to fill a field it then discards.
|
|
465
|
-
async list() {
|
|
466
|
-
if (!client.list) {
|
|
467
|
-
throw new Error("daytona sandbox client does not support listing");
|
|
468
|
-
}
|
|
469
|
-
const targets = [];
|
|
470
|
-
const seen = /* @__PURE__ */ new Set();
|
|
471
|
-
for (let page = 1; page <= MAX_LIST_PAGES; page++) {
|
|
472
|
-
const result = await client.list(void 0, page, LIST_PAGE_SIZE);
|
|
473
|
-
const items = result?.items ?? [];
|
|
474
|
-
for (const sandbox of items) {
|
|
475
|
-
const labels = sandbox.labels;
|
|
476
|
-
const workspaceId = labels?.workspaceId ?? labels?.["claxedo.workspaceId"];
|
|
477
|
-
if (!workspaceId || seen.has(sandbox.id)) continue;
|
|
478
|
-
seen.add(sandbox.id);
|
|
479
|
-
targets.push({
|
|
480
|
-
workspaceId,
|
|
481
|
-
sandboxId: sandbox.id,
|
|
482
|
-
// Identity, not a reachable address — never routed through.
|
|
483
|
-
url: sandbox.id,
|
|
484
|
-
// Must equal what `ensureHost` stored on the lease, or a LIVE
|
|
485
|
-
// sandbox fails GC's identity check and gets destroyed. Both paths
|
|
486
|
-
// derive it from `labelName(workspaceId)`.
|
|
487
|
-
hostId: labelName(workspaceId),
|
|
488
|
-
driverResourceId: sandbox.id,
|
|
489
|
-
labels,
|
|
490
|
-
driver: { id: "daytona", resourceId: sandbox.id }
|
|
491
|
-
});
|
|
492
|
-
}
|
|
493
|
-
if (items.length < LIST_PAGE_SIZE) break;
|
|
494
|
-
}
|
|
495
|
-
return targets;
|
|
496
|
-
},
|
|
497
|
-
async resumeHost(input) {
|
|
498
|
-
const sandbox = await sandboxById(input.lease.sandboxId);
|
|
499
|
-
if (!await ensureStarted(sandbox)) return { provisioning: true, retryAfterMs: 2e3 };
|
|
500
|
-
if (await applyNetworkPolicy(sandbox, input.ensure) === "retry") {
|
|
501
|
-
return { provisioning: true, retryAfterMs: 2e3 };
|
|
502
|
-
}
|
|
503
|
-
return readyTarget(input.ensure, sandbox, input.ensure.hostId ?? labelName(input.ensure.workspaceId));
|
|
504
|
-
},
|
|
505
|
-
async touch(target) {
|
|
506
|
-
await sandboxById(target.sandboxId).then((sandbox) => sandbox.refreshActivity()).catch(() => void 0);
|
|
507
|
-
},
|
|
508
|
-
async suspend(target) {
|
|
509
|
-
await sandboxById(target.sandboxId).then((sandbox) => sandbox.stop(operationTimeout)).catch(() => void 0);
|
|
510
|
-
},
|
|
511
|
-
async stop(target) {
|
|
512
|
-
await sandboxById(target.sandboxId).then((sandbox) => sandbox.stop(operationTimeout)).catch(() => void 0);
|
|
513
|
-
},
|
|
514
|
-
async destroy(target) {
|
|
515
|
-
await sandboxById(target.sandboxId).then((sandbox) => sandbox.delete(operationTimeout)).catch((err) => {
|
|
516
|
-
const status = err.response?.status ?? err.status;
|
|
517
|
-
if (status !== 404) throw err;
|
|
518
|
-
});
|
|
519
|
-
},
|
|
520
|
-
async snapshot(target) {
|
|
521
|
-
const sandbox = await sandboxById(target.sandboxId);
|
|
522
|
-
if (!sandbox._experimental_createSnapshot) {
|
|
523
|
-
throw new Error("Daytona sandbox client does not support filesystem snapshots");
|
|
524
|
-
}
|
|
525
|
-
const snapshotId = `claxedo-${target.workspaceId ?? target.sandboxId}-${Date.now()}`;
|
|
526
|
-
await sandbox._experimental_createSnapshot(snapshotId, operationTimeout);
|
|
527
|
-
return { snapshotId };
|
|
528
|
-
}
|
|
529
|
-
};
|
|
530
|
-
}
|
|
531
|
-
function network(input) {
|
|
532
|
-
if (!input.net || input.net.mode === "allow-all") return {};
|
|
533
|
-
const hosts = input.net.hosts ?? [];
|
|
534
|
-
if (hosts.length > 0) return { domainAllowList: formatDaytonaDomainAllowList(hosts) };
|
|
535
|
-
const cidrs = input.net.cidrs ?? [];
|
|
536
|
-
if (cidrs.length === 0) return { networkBlockAll: true };
|
|
537
|
-
return { networkAllowList: formatDaytonaAllowList(cidrs) };
|
|
538
|
-
}
|
|
539
|
-
export {
|
|
540
|
-
createDaytonaSandboxDriver
|
|
541
|
-
};
|
package/dist/drivers/exe.d.ts
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import type { SandboxDriver, SandboxDriverEnsureInput } from "..";
|
|
2
|
-
type ExeVm = {
|
|
3
|
-
vm_name: string;
|
|
4
|
-
https_url?: string;
|
|
5
|
-
ssh_dest?: string;
|
|
6
|
-
status?: string;
|
|
7
|
-
/** Provider-side tags, written at create — the durable half of our labels. */
|
|
8
|
-
tags?: string[];
|
|
9
|
-
};
|
|
10
|
-
export type ExeSandboxDriverOptions = {
|
|
11
|
-
apiToken: string;
|
|
12
|
-
endpoint?: string;
|
|
13
|
-
image?: string;
|
|
14
|
-
runtimeCommand?: string;
|
|
15
|
-
workspaceDir?: string;
|
|
16
|
-
runtimePort?: number;
|
|
17
|
-
runner?: string;
|
|
18
|
-
operationTimeoutMs?: number;
|
|
19
|
-
healthTimeoutMs?: number;
|
|
20
|
-
healthIntervalMs?: number;
|
|
21
|
-
fetchImpl?: typeof fetch;
|
|
22
|
-
env?: (input: SandboxDriverEnsureInput, vm: ExeVm) => Record<string, string> | Promise<Record<string, string>>;
|
|
23
|
-
};
|
|
24
|
-
export declare function exeWorkspaceName(workspaceId: string, epoch: number): string;
|
|
25
|
-
export declare function createExeSandboxDriver(options: ExeSandboxDriverOptions): SandboxDriver;
|
|
26
|
-
export {};
|