@claxedo/sandbox-manager 0.9.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 +233 -26
- 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 +42 -7
- package/dist/driver-catalog.mjs +115 -161
- 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 +3 -3
- package/dist/drivers/cloudflare.mjs +208 -147
- package/dist/drivers/docker.d.ts +4 -3
- package/dist/drivers/docker.mjs +154 -126
- package/dist/drivers/local-brokering.d.ts +14 -0
- package/dist/drivers/local-brokering.mjs +1029 -0
- package/dist/drivers/modal.d.ts +2 -2
- package/dist/drivers/modal.mjs +121 -111
- package/dist/drivers/transient-error.d.ts +2 -0
- package/dist/drivers/vercel.d.ts +3 -2
- package/dist/drivers/vercel.mjs +151 -115
- 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 +9 -2
- package/dist/hosted-network-policy.mjs +218 -1
- package/dist/image-name.d.ts +12 -2
- package/dist/image-name.mjs +14 -5
- package/dist/image.d.ts +1 -20
- package/dist/image.mjs +11 -88
- package/dist/index.d.ts +8 -578
- package/dist/index.mjs +1379 -320
- 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 +4 -3
- package/dist/manager.d.ts +2 -0
- package/dist/runtime-env.d.ts +4 -2
- package/dist/runtime-env.mjs +37 -2
- 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 +0 -1
- package/dist/service-url-exposure.mjs +0 -9
- 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 +63 -52
- package/package.json +26 -42
- 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 -574
- package/dist/drivers/daytona.d.ts +0 -163
- package/dist/drivers/daytona.mjs +0 -683
- package/dist/drivers/exe.d.ts +0 -26
- package/dist/drivers/exe.mjs +0 -492
- package/dist/drivers/fetch-bridge.d.ts +0 -10
- package/dist/drivers/fetch-bridge.mjs +0 -126
- package/dist/lease-policy.d.ts +0 -52
- package/dist/lease-policy.mjs +0 -290
package/dist/drivers/daytona.mjs
DELETED
|
@@ -1,683 +0,0 @@
|
|
|
1
|
-
// src/daytona-allow-list.ts
|
|
2
|
-
function formatDaytonaAllowList(cidrs) {
|
|
3
|
-
return cidrs.slice(0, 10).join(",");
|
|
4
|
-
}
|
|
5
|
-
function formatDaytonaDomainAllowList(hosts) {
|
|
6
|
-
return [...new Set(hosts)].join(",");
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
// src/runtime-env.ts
|
|
10
|
-
function workspaceRuntimeBootEnv(input) {
|
|
11
|
-
const env = {
|
|
12
|
-
...workspaceRuntimeTargetEnv(input),
|
|
13
|
-
...workspaceRuntimeSourceEnv(input),
|
|
14
|
-
...input.env
|
|
15
|
-
};
|
|
16
|
-
if (input.runner) env.WORKSPACE_RUNTIME_RUNNER = input.runner;
|
|
17
|
-
if (input.controlEnv?.relayJwksUrl) env.WORKSPACE_RUNTIME_RELAY_JWKS_URL = input.controlEnv.relayJwksUrl;
|
|
18
|
-
if (input.controlEnv?.relayVerifyPem) env.WORKSPACE_RUNTIME_RELAY_HOST_VERIFY_PEM = input.controlEnv.relayVerifyPem;
|
|
19
|
-
if (input.controlEnv?.managementJwksUrl) env.WORKSPACE_RUNTIME_MANAGEMENT_JWKS_URL = input.controlEnv.managementJwksUrl;
|
|
20
|
-
if (input.controlEnv?.sessionAuthorityUrl) env.WORKSPACE_RUNTIME_SESSION_AUTHORITY_URL = input.controlEnv.sessionAuthorityUrl;
|
|
21
|
-
return env;
|
|
22
|
-
}
|
|
23
|
-
function workspaceRuntimeTargetEnv(input) {
|
|
24
|
-
return {
|
|
25
|
-
WORKSPACE_RUNTIME_WORKSPACE_ID: input.workspaceId,
|
|
26
|
-
...input.hostId ? { WORKSPACE_RUNTIME_HOST_ID: input.hostId } : {},
|
|
27
|
-
WORKSPACE_RUNTIME_DIRECTORY: input.directory,
|
|
28
|
-
WORKSPACE_RUNTIME_PORT: String(input.port),
|
|
29
|
-
...input.host ? { WORKSPACE_RUNTIME_HOST: input.host } : {}
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
function workspaceRuntimeSourceEnv(input) {
|
|
33
|
-
if (!input.source || input.source.kind === "empty") return { WORKSPACE_RUNTIME_SOURCE_KIND: "empty" };
|
|
34
|
-
return {
|
|
35
|
-
WORKSPACE_RUNTIME_SOURCE_KIND: "git",
|
|
36
|
-
WORKSPACE_RUNTIME_GIT_REPO_URL: input.source.repoUrl,
|
|
37
|
-
...input.source.branch ? { WORKSPACE_RUNTIME_GIT_BRANCH: input.source.branch } : {}
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// src/command.ts
|
|
42
|
-
function shell(input) {
|
|
43
|
-
return `'${input.replace(/'/g, `'"'"'`)}'`;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// src/constants.ts
|
|
47
|
-
var DEFAULT_WORKSPACE_RUNTIME_PORT = 2593;
|
|
48
|
-
|
|
49
|
-
// src/hosted-network-policy.ts
|
|
50
|
-
var SANDBOX_MODEL_PROVIDER_HOSTS = [
|
|
51
|
-
"api.anthropic.com",
|
|
52
|
-
"api.openai.com",
|
|
53
|
-
"generativelanguage.googleapis.com",
|
|
54
|
-
"openrouter.ai",
|
|
55
|
-
// Model catalog + engine config, fetched on runner startup.
|
|
56
|
-
"models.dev",
|
|
57
|
-
"opencode.ai"
|
|
58
|
-
];
|
|
59
|
-
var SANDBOX_PACKAGE_REGISTRY_HOSTS = [
|
|
60
|
-
"registry.npmjs.org",
|
|
61
|
-
"nodejs.org",
|
|
62
|
-
"pypi.org",
|
|
63
|
-
"files.pythonhosted.org",
|
|
64
|
-
"crates.io",
|
|
65
|
-
"static.crates.io",
|
|
66
|
-
"index.crates.io",
|
|
67
|
-
"proxy.golang.org",
|
|
68
|
-
"sum.golang.org"
|
|
69
|
-
];
|
|
70
|
-
var SANDBOX_BASELINE_EGRESS_HOSTS = [
|
|
71
|
-
...SANDBOX_MODEL_PROVIDER_HOSTS,
|
|
72
|
-
...SANDBOX_PACKAGE_REGISTRY_HOSTS
|
|
73
|
-
];
|
|
74
|
-
|
|
75
|
-
// ../sandbox-contract/dist/index.mjs
|
|
76
|
-
var sandboxDriverCredentialFields = {
|
|
77
|
-
exe: [{ key: "api_token", label: "API Token", secret: true }],
|
|
78
|
-
daytona: [{ key: "api_key", label: "API Key", secret: true }],
|
|
79
|
-
modal: [
|
|
80
|
-
{ key: "token_id", label: "Token ID", secret: true },
|
|
81
|
-
{ key: "token_secret", label: "Token Secret", secret: true }
|
|
82
|
-
],
|
|
83
|
-
vercel: [
|
|
84
|
-
{ key: "access_token", label: "Access Token", secret: true },
|
|
85
|
-
{ key: "team_id", label: "Team ID" },
|
|
86
|
-
{ key: "project_id", label: "Project ID" }
|
|
87
|
-
],
|
|
88
|
-
cloudflare: [
|
|
89
|
-
{ key: "api_token", label: "API Token", secret: true },
|
|
90
|
-
{ key: "worker_url", label: "Worker URL" }
|
|
91
|
-
],
|
|
92
|
-
box: [{ key: "api_key", label: "API Key", secret: true }],
|
|
93
|
-
docker: [{ key: "image", label: "Image" }]
|
|
94
|
-
};
|
|
95
|
-
var sandboxDriverLabels = {
|
|
96
|
-
exe: "exe.dev",
|
|
97
|
-
daytona: "Daytona",
|
|
98
|
-
modal: "Modal",
|
|
99
|
-
vercel: "Vercel",
|
|
100
|
-
cloudflare: "Cloudflare",
|
|
101
|
-
box: "Box",
|
|
102
|
-
docker: "Docker"
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
// src/driver-catalog.ts
|
|
106
|
-
var sandboxDriverCatalog = {
|
|
107
|
-
exe: {
|
|
108
|
-
id: "exe",
|
|
109
|
-
label: sandboxDriverLabels.exe,
|
|
110
|
-
credentialFields: sandboxDriverCredentialFields.exe,
|
|
111
|
-
metadata: {
|
|
112
|
-
driverRunsIn: ["worker", "node"],
|
|
113
|
-
hostStopBehavior: "not-supported",
|
|
114
|
-
hostResumeBehavior: "same-host",
|
|
115
|
-
targetAccess: "relay",
|
|
116
|
-
secretBrokering: "none",
|
|
117
|
-
egressControl: "none",
|
|
118
|
-
persistence: {
|
|
119
|
-
resume: "same-sandbox",
|
|
120
|
-
capture: "none",
|
|
121
|
-
clone: true,
|
|
122
|
-
captureSource: "not-applicable",
|
|
123
|
-
retention: "provider-managed",
|
|
124
|
-
restoreMount: "same-resource"
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
},
|
|
128
|
-
daytona: {
|
|
129
|
-
id: "daytona",
|
|
130
|
-
label: sandboxDriverLabels.daytona,
|
|
131
|
-
credentialFields: sandboxDriverCredentialFields.daytona,
|
|
132
|
-
metadata: {
|
|
133
|
-
driverRunsIn: ["worker", "node"],
|
|
134
|
-
hostStopBehavior: "suspends-host",
|
|
135
|
-
hostResumeBehavior: "same-host",
|
|
136
|
-
targetAccess: "relay",
|
|
137
|
-
secretBrokering: "native",
|
|
138
|
-
// The driver translates names to domainAllowList, or addresses to networkAllowList.
|
|
139
|
-
egressControl: "hosts-and-cidrs",
|
|
140
|
-
persistence: {
|
|
141
|
-
resume: "same-sandbox",
|
|
142
|
-
capture: "filesystem",
|
|
143
|
-
clone: false,
|
|
144
|
-
captureSource: "preserved",
|
|
145
|
-
retention: "provider-managed",
|
|
146
|
-
restoreMount: "new-resource"
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
},
|
|
150
|
-
modal: {
|
|
151
|
-
id: "modal",
|
|
152
|
-
label: sandboxDriverLabels.modal,
|
|
153
|
-
credentialFields: sandboxDriverCredentialFields.modal,
|
|
154
|
-
metadata: {
|
|
155
|
-
driverRunsIn: ["node"],
|
|
156
|
-
hostStopBehavior: "terminates-host",
|
|
157
|
-
hostResumeBehavior: "replacement-host",
|
|
158
|
-
targetAccess: "relay",
|
|
159
|
-
secretBrokering: "none",
|
|
160
|
-
// The driver does not install Modal's domain allowlist or alpha sidecar.
|
|
161
|
-
egressControl: "none",
|
|
162
|
-
persistence: {
|
|
163
|
-
resume: "replacement-restore",
|
|
164
|
-
capture: "filesystem",
|
|
165
|
-
clone: false,
|
|
166
|
-
captureSource: "preserved",
|
|
167
|
-
retention: "provider-managed",
|
|
168
|
-
restoreMount: "new-resource"
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
},
|
|
172
|
-
vercel: {
|
|
173
|
-
id: "vercel",
|
|
174
|
-
label: sandboxDriverLabels.vercel,
|
|
175
|
-
credentialFields: sandboxDriverCredentialFields.vercel,
|
|
176
|
-
metadata: {
|
|
177
|
-
driverRunsIn: ["node"],
|
|
178
|
-
hostStopBehavior: "terminates-host",
|
|
179
|
-
hostResumeBehavior: "replacement-host",
|
|
180
|
-
targetAccess: "relay",
|
|
181
|
-
secretBrokering: "native",
|
|
182
|
-
// The driver translates hosts only; the SDK's subnet rules are not wired to net.cidrs.
|
|
183
|
-
egressControl: "hosts",
|
|
184
|
-
persistence: {
|
|
185
|
-
resume: "replacement-restore",
|
|
186
|
-
capture: "filesystem",
|
|
187
|
-
clone: false,
|
|
188
|
-
captureSource: "stopped",
|
|
189
|
-
retention: "explicit",
|
|
190
|
-
restoreMount: "new-resource"
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
},
|
|
194
|
-
cloudflare: {
|
|
195
|
-
id: "cloudflare",
|
|
196
|
-
label: sandboxDriverLabels.cloudflare,
|
|
197
|
-
credentialFields: sandboxDriverCredentialFields.cloudflare,
|
|
198
|
-
metadata: {
|
|
199
|
-
driverRunsIn: ["worker"],
|
|
200
|
-
hostStopBehavior: "not-supported",
|
|
201
|
-
hostResumeBehavior: "same-host",
|
|
202
|
-
targetAccess: "relay",
|
|
203
|
-
secretBrokering: "native",
|
|
204
|
-
// Credential handlers do not restrict unrelated destinations.
|
|
205
|
-
egressControl: "none",
|
|
206
|
-
persistence: {
|
|
207
|
-
resume: "replacement-restore",
|
|
208
|
-
capture: "directories",
|
|
209
|
-
clone: false,
|
|
210
|
-
captureSource: "preserved",
|
|
211
|
-
retention: "provider-managed",
|
|
212
|
-
restoreMount: "copy-on-write"
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
},
|
|
216
|
-
box: {
|
|
217
|
-
id: "box",
|
|
218
|
-
label: sandboxDriverLabels.box,
|
|
219
|
-
credentialFields: sandboxDriverCredentialFields.box,
|
|
220
|
-
metadata: {
|
|
221
|
-
driverRunsIn: ["node"],
|
|
222
|
-
hostStopBehavior: "suspends-host",
|
|
223
|
-
hostResumeBehavior: "same-host",
|
|
224
|
-
targetAccess: "relay",
|
|
225
|
-
secretBrokering: "none",
|
|
226
|
-
egressControl: "none",
|
|
227
|
-
persistence: {
|
|
228
|
-
resume: "same-sandbox",
|
|
229
|
-
capture: "none",
|
|
230
|
-
clone: false,
|
|
231
|
-
captureSource: "not-applicable",
|
|
232
|
-
retention: "provider-managed",
|
|
233
|
-
restoreMount: "same-resource"
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
},
|
|
237
|
-
docker: {
|
|
238
|
-
id: "docker",
|
|
239
|
-
label: sandboxDriverLabels.docker,
|
|
240
|
-
credentialFields: sandboxDriverCredentialFields.docker,
|
|
241
|
-
metadata: {
|
|
242
|
-
driverRunsIn: ["local"],
|
|
243
|
-
hostStopBehavior: "terminates-host",
|
|
244
|
-
hostResumeBehavior: "same-host",
|
|
245
|
-
targetAccess: "loopback",
|
|
246
|
-
secretBrokering: "none",
|
|
247
|
-
egressControl: "none",
|
|
248
|
-
persistence: {
|
|
249
|
-
resume: "replacement-restore",
|
|
250
|
-
capture: "same-resource",
|
|
251
|
-
clone: false,
|
|
252
|
-
captureSource: "preserved",
|
|
253
|
-
retention: "provider-managed",
|
|
254
|
-
restoreMount: "same-resource"
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
};
|
|
259
|
-
|
|
260
|
-
// ../claxedo-helpers/dist/guards.mjs
|
|
261
|
-
function isRecord(value) {
|
|
262
|
-
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
// src/json.ts
|
|
266
|
-
function num(input) {
|
|
267
|
-
return typeof input === "number" ? input : void 0;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
// src/drivers/transient-error.ts
|
|
271
|
-
function textOf(input) {
|
|
272
|
-
return typeof input === "string" ? input : "";
|
|
273
|
-
}
|
|
274
|
-
function driverErrorSignals(err) {
|
|
275
|
-
const shaped = isRecord(err) ? err : {};
|
|
276
|
-
const nested = isRecord(shaped.response) ? shaped.response : {};
|
|
277
|
-
return {
|
|
278
|
-
status: num(nested.status) ?? num(shaped.status),
|
|
279
|
-
text: `${textOf(shaped.code)} ${textOf(shaped.name)} ${textOf(shaped.message)}`.toLowerCase()
|
|
280
|
-
};
|
|
281
|
-
}
|
|
282
|
-
function isTransientDriverError(err, markers) {
|
|
283
|
-
const { status, text } = driverErrorSignals(err);
|
|
284
|
-
if (status !== void 0 && status >= 500) return true;
|
|
285
|
-
return markers.some((marker) => text.includes(marker));
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
// src/drivers/daytona.ts
|
|
289
|
-
var DEFAULT_RUNTIME_COMMAND = "/usr/local/bin/workspace-runtime";
|
|
290
|
-
var DEFAULT_WORKSPACE_DIR = "/workspace";
|
|
291
|
-
var DEFAULT_PREVIEW_EXPIRY_S = 3600;
|
|
292
|
-
var DEFAULT_OPERATION_TIMEOUT_S = 60;
|
|
293
|
-
var LIST_PAGE_SIZE = 100;
|
|
294
|
-
var SECRET_LIST_PAGE_SIZE = 200;
|
|
295
|
-
var SENTINEL_SECRET_ENV = "CLAXEDO_BROKERED_SECRET_SLOT";
|
|
296
|
-
var REVOKED_SECRET_VALUE = "claxedo-revoked";
|
|
297
|
-
var MAX_LIST_PAGES = 100;
|
|
298
|
-
function labelName(workspaceId) {
|
|
299
|
-
return `claxedo-${workspaceId}`;
|
|
300
|
-
}
|
|
301
|
-
function encodeSecretSegment(value) {
|
|
302
|
-
return encodeURIComponent(value).replace(/[-_.!~*'()]/g, (char) => `%${char.charCodeAt(0).toString(16).toUpperCase()}`).replace(/%/g, "_");
|
|
303
|
-
}
|
|
304
|
-
function workspaceSecretPrefix(workspaceId) {
|
|
305
|
-
return `claxedo-${encodeSecretSegment(workspaceId)}-`;
|
|
306
|
-
}
|
|
307
|
-
function daytonaSecretName(workspaceId, secretName) {
|
|
308
|
-
return `${workspaceSecretPrefix(workspaceId)}${encodeSecretSegment(secretName)}`;
|
|
309
|
-
}
|
|
310
|
-
function daytonaSecretEnvName(workspaceId, secretName) {
|
|
311
|
-
try {
|
|
312
|
-
return decodeURIComponent(secretName.slice(workspaceSecretPrefix(workspaceId).length).replace(/_/g, "%"));
|
|
313
|
-
} catch {
|
|
314
|
-
return void 0;
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
async function listWorkspaceSecrets(secrets, workspaceId) {
|
|
318
|
-
const prefix = workspaceSecretPrefix(workspaceId);
|
|
319
|
-
const held = /* @__PURE__ */ new Map();
|
|
320
|
-
let cursor;
|
|
321
|
-
for (let page = 0; page < MAX_LIST_PAGES; page++) {
|
|
322
|
-
const result = await secrets.list({ name: prefix, limit: SECRET_LIST_PAGE_SIZE, ...cursor ? { cursor } : {} });
|
|
323
|
-
for (const secret of result.items ?? []) {
|
|
324
|
-
if (secret.name.startsWith(prefix)) held.set(secret.name, secret);
|
|
325
|
-
}
|
|
326
|
-
cursor = result.nextCursor ?? void 0;
|
|
327
|
-
if (!cursor) break;
|
|
328
|
-
}
|
|
329
|
-
return held;
|
|
330
|
-
}
|
|
331
|
-
async function withdrawSecret(secrets, secret, options) {
|
|
332
|
-
await secrets.update(secret.id, { value: REVOKED_SECRET_VALUE, hosts: [] });
|
|
333
|
-
if (options.delete) await secrets.delete(secret.id);
|
|
334
|
-
}
|
|
335
|
-
var TRANSIENT_MARKERS = ["timeout", "pending", "starting"];
|
|
336
|
-
function transientDriverError(err) {
|
|
337
|
-
return isTransientDriverError(err, TRANSIENT_MARKERS);
|
|
338
|
-
}
|
|
339
|
-
async function createDefaultClient(options) {
|
|
340
|
-
const { Daytona } = await import("@daytona/sdk");
|
|
341
|
-
const sdk = new Daytona({
|
|
342
|
-
apiKey: options.apiKey,
|
|
343
|
-
...options.apiUrl ? { apiUrl: options.apiUrl } : {},
|
|
344
|
-
...options.organizationId ? { organizationId: options.organizationId } : {},
|
|
345
|
-
...options.target ? { target: options.target } : {},
|
|
346
|
-
_experimental: { otelEnabled: false }
|
|
347
|
-
});
|
|
348
|
-
return {
|
|
349
|
-
async findByLabels(labels) {
|
|
350
|
-
for await (const sandbox of sdk.list({ labels })) return sandbox;
|
|
351
|
-
return void 0;
|
|
352
|
-
},
|
|
353
|
-
// Page 1 carries everything; later pages are empty. See DaytonaClientLike.list.
|
|
354
|
-
async list(labels, page = 1, limit) {
|
|
355
|
-
if (page > 1) return { items: [] };
|
|
356
|
-
const items = [];
|
|
357
|
-
for await (const sandbox of sdk.list({ ...labels ? { labels } : {}, ...limit ? { limit } : {} })) {
|
|
358
|
-
items.push(sandbox);
|
|
359
|
-
}
|
|
360
|
-
return { items };
|
|
361
|
-
},
|
|
362
|
-
create(params, operation) {
|
|
363
|
-
if (params.image) return sdk.create({ ...params, image: params.image }, operation);
|
|
364
|
-
return sdk.create(params, operation);
|
|
365
|
-
},
|
|
366
|
-
get: (sandboxIdOrName) => sdk.get(sandboxIdOrName),
|
|
367
|
-
secret: sdk.secret
|
|
368
|
-
};
|
|
369
|
-
}
|
|
370
|
-
function createDaytonaSandboxDriver(options) {
|
|
371
|
-
let defaultClient;
|
|
372
|
-
function resolveClient() {
|
|
373
|
-
if (options.client) return options.client;
|
|
374
|
-
defaultClient ??= createDefaultClient(options);
|
|
375
|
-
return defaultClient;
|
|
376
|
-
}
|
|
377
|
-
const runtimePort = options.runtimePort ?? DEFAULT_WORKSPACE_RUNTIME_PORT;
|
|
378
|
-
const runtimeCommand = options.runtimeCommand ?? DEFAULT_RUNTIME_COMMAND;
|
|
379
|
-
const workspaceDir = options.workspaceDir ?? DEFAULT_WORKSPACE_DIR;
|
|
380
|
-
const previewExpiry = options.previewExpirySeconds ?? DEFAULT_PREVIEW_EXPIRY_S;
|
|
381
|
-
const operationTimeout = options.operationTimeoutSeconds ?? DEFAULT_OPERATION_TIMEOUT_S;
|
|
382
|
-
const warn = options.warn ?? ((message) => console.warn(message));
|
|
383
|
-
function workspaceDirectory(input) {
|
|
384
|
-
return input.workspaceRoot ?? workspaceDir;
|
|
385
|
-
}
|
|
386
|
-
function staticBootEnv(input, hostId, directory) {
|
|
387
|
-
return workspaceRuntimeBootEnv({
|
|
388
|
-
workspaceId: input.workspaceId,
|
|
389
|
-
hostId,
|
|
390
|
-
directory,
|
|
391
|
-
port: runtimePort,
|
|
392
|
-
source: input.source,
|
|
393
|
-
env: input.env,
|
|
394
|
-
runner: options.runner,
|
|
395
|
-
controlEnv: options.controlEnv
|
|
396
|
-
});
|
|
397
|
-
}
|
|
398
|
-
async function bootEnv(input, sandbox, hostId) {
|
|
399
|
-
return {
|
|
400
|
-
...staticBootEnv(input, hostId, workspaceDirectory(input)),
|
|
401
|
-
...await options.env?.(input, sandbox)
|
|
402
|
-
};
|
|
403
|
-
}
|
|
404
|
-
async function findExisting(workspaceId) {
|
|
405
|
-
const client = await resolveClient();
|
|
406
|
-
const labels = { "claxedo.workspaceId": workspaceId };
|
|
407
|
-
if (client.findByLabels) return client.findByLabels(labels).catch(() => void 0);
|
|
408
|
-
const result = await client.list?.(labels, 1, 1).catch(() => void 0);
|
|
409
|
-
return result?.items?.[0];
|
|
410
|
-
}
|
|
411
|
-
async function sandboxById(sandboxId) {
|
|
412
|
-
return (await resolveClient()).get(sandboxId);
|
|
413
|
-
}
|
|
414
|
-
async function previewUrl(sandbox) {
|
|
415
|
-
const signed = await sandbox.getSignedPreviewUrl(runtimePort, previewExpiry).catch(() => void 0);
|
|
416
|
-
if (signed?.url) return signed;
|
|
417
|
-
return sandbox.getPreviewLink(runtimePort).catch(() => void 0);
|
|
418
|
-
}
|
|
419
|
-
async function startRuntime(sandbox, input, env) {
|
|
420
|
-
const directory = workspaceDirectory(input);
|
|
421
|
-
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`;
|
|
422
|
-
await sandbox.process.executeCommand(`sh -lc ${shell(script)}`, directory, env, operationTimeout).catch(() => void 0);
|
|
423
|
-
}
|
|
424
|
-
async function ensureStarted(sandbox) {
|
|
425
|
-
if (!sandbox.state || sandbox.state === "started") return true;
|
|
426
|
-
if (sandbox.state === "starting") return false;
|
|
427
|
-
await sandbox.start(operationTimeout).catch(() => void 0);
|
|
428
|
-
return sandbox.state === "started";
|
|
429
|
-
}
|
|
430
|
-
async function readyTarget(input, sandbox, hostId) {
|
|
431
|
-
await startRuntime(sandbox, input, await bootEnv(input, sandbox, hostId));
|
|
432
|
-
const preview = await previewUrl(sandbox);
|
|
433
|
-
if (!preview?.url) return { provisioning: true, retryAfterMs: 2e3 };
|
|
434
|
-
const target = {
|
|
435
|
-
workspaceId: input.workspaceId,
|
|
436
|
-
sandboxId: sandbox.id,
|
|
437
|
-
url: preview.url,
|
|
438
|
-
hostId,
|
|
439
|
-
driverResourceId: sandbox.id,
|
|
440
|
-
driver: {
|
|
441
|
-
id: "daytona",
|
|
442
|
-
resourceId: sandbox.id
|
|
443
|
-
},
|
|
444
|
-
labels: { ...input.labels, ...preview.token ? { "daytona.previewToken": preview.token } : {} }
|
|
445
|
-
};
|
|
446
|
-
return target;
|
|
447
|
-
}
|
|
448
|
-
async function brokeredSecretService() {
|
|
449
|
-
const secrets = (await resolveClient()).secret;
|
|
450
|
-
if (!secrets) {
|
|
451
|
-
throw new Error("daytona sandbox client does not support secret brokering");
|
|
452
|
-
}
|
|
453
|
-
return secrets;
|
|
454
|
-
}
|
|
455
|
-
async function reconcileBrokeredSecrets(input, options2) {
|
|
456
|
-
const secrets = await brokeredSecretService();
|
|
457
|
-
const existing = await listWorkspaceSecrets(secrets, input.workspaceId);
|
|
458
|
-
const references = {};
|
|
459
|
-
const desired = /* @__PURE__ */ new Set();
|
|
460
|
-
const sentinel = daytonaSecretName(input.workspaceId, SENTINEL_SECRET_ENV);
|
|
461
|
-
desired.add(sentinel);
|
|
462
|
-
references[SENTINEL_SECRET_ENV] = sentinel;
|
|
463
|
-
if (!existing.has(sentinel)) await secrets.create({ name: sentinel, value: "", hosts: [] });
|
|
464
|
-
for (const secret of input.secrets ?? []) {
|
|
465
|
-
if (secret.hosts.length === 0) {
|
|
466
|
-
throw new Error(`daytona brokered secret "${secret.name}" requires at least one host in its egress allowlist`);
|
|
467
|
-
}
|
|
468
|
-
const name = daytonaSecretName(input.workspaceId, secret.name);
|
|
469
|
-
desired.add(name);
|
|
470
|
-
references[secret.name] = name;
|
|
471
|
-
const current = existing.get(name);
|
|
472
|
-
if (current) await secrets.update(current.id, { value: secret.value, hosts: secret.hosts });
|
|
473
|
-
else await secrets.create({ name, value: secret.value, hosts: secret.hosts });
|
|
474
|
-
}
|
|
475
|
-
if (options2.withdraw) {
|
|
476
|
-
for (const [name, secret] of existing) {
|
|
477
|
-
if (desired.has(name)) continue;
|
|
478
|
-
await withdrawSecret(secrets, secret, { delete: false });
|
|
479
|
-
if (!options2.mountWithdrawn) continue;
|
|
480
|
-
const envName = daytonaSecretEnvName(input.workspaceId, name);
|
|
481
|
-
if (!envName) {
|
|
482
|
-
warn(
|
|
483
|
-
`[sandbox-manager] daytona org secret ${name} matches workspace ${input.workspaceId}'s prefix but was not minted by this driver; it was emptied and left unmounted`
|
|
484
|
-
);
|
|
485
|
-
continue;
|
|
486
|
-
}
|
|
487
|
-
desired.add(name);
|
|
488
|
-
references[envName] = name;
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
const mountedNamesChanged = existing.size !== desired.size || [...desired].some((name) => !existing.has(name));
|
|
492
|
-
return { references, mountedNamesChanged };
|
|
493
|
-
}
|
|
494
|
-
function mayBeRunning(sandbox) {
|
|
495
|
-
return !sandbox.state || sandbox.state === "started";
|
|
496
|
-
}
|
|
497
|
-
async function restartForMountedSecrets(sandbox, workspaceId) {
|
|
498
|
-
warn(
|
|
499
|
-
`[sandbox-manager] restarting daytona sandbox ${sandbox.id} (workspace ${workspaceId}): the set of brokered secret names changed and mounted env vars only reach processes spawned after the change`
|
|
500
|
-
);
|
|
501
|
-
await sandbox.stop(operationTimeout);
|
|
502
|
-
await sandbox.start(operationTimeout);
|
|
503
|
-
}
|
|
504
|
-
async function applyBrokeredSecrets(sandbox, plan, workspaceId) {
|
|
505
|
-
await sandbox.updateSecrets(plan.references);
|
|
506
|
-
if (plan.mountedNamesChanged && mayBeRunning(sandbox)) {
|
|
507
|
-
await restartForMountedSecrets(sandbox, workspaceId);
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
async function applyNetworkPolicy(sandbox, input) {
|
|
511
|
-
const net = network(input);
|
|
512
|
-
if (Object.keys(net).length === 0) return "applied";
|
|
513
|
-
if (!sandbox.updateNetworkSettings) {
|
|
514
|
-
throw new Error(
|
|
515
|
-
`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`
|
|
516
|
-
);
|
|
517
|
-
}
|
|
518
|
-
try {
|
|
519
|
-
await sandbox.updateNetworkSettings(net);
|
|
520
|
-
} catch (err) {
|
|
521
|
-
if (transientDriverError(err)) return "retry";
|
|
522
|
-
throw err;
|
|
523
|
-
}
|
|
524
|
-
return "applied";
|
|
525
|
-
}
|
|
526
|
-
async function ensureHost(input) {
|
|
527
|
-
const hostId = labelName(input.workspaceId);
|
|
528
|
-
const bootSource = input.bootSource?.kind === "image" ? { image: input.bootSource.image } : { snapshot: input.bootSource?.kind === "driver-snapshot" ? input.bootSource.snapshotId : input.snapshot ?? options.baseSnapshot };
|
|
529
|
-
const net = network(input);
|
|
530
|
-
const existing = await findExisting(input.workspaceId);
|
|
531
|
-
const plan = existing && input.secrets === void 0 ? void 0 : await reconcileBrokeredSecrets(input, {
|
|
532
|
-
withdraw: input.secrets !== void 0,
|
|
533
|
-
mountWithdrawn: Boolean(existing)
|
|
534
|
-
});
|
|
535
|
-
const sandbox = existing ?? await (await resolveClient()).create({
|
|
536
|
-
name: labelName(input.workspaceId),
|
|
537
|
-
...bootSource,
|
|
538
|
-
envVars: staticBootEnv(input, hostId, workspaceDirectory(input)),
|
|
539
|
-
...plan ? { secrets: plan.references } : {},
|
|
540
|
-
labels: { ...input.labels, "claxedo.workspaceId": input.workspaceId },
|
|
541
|
-
public: false,
|
|
542
|
-
...net,
|
|
543
|
-
...options.autoStopMinutes !== void 0 ? { autoStopInterval: options.autoStopMinutes } : {},
|
|
544
|
-
...options.autoDeleteMinutes !== void 0 ? { autoDeleteInterval: options.autoDeleteMinutes } : {}
|
|
545
|
-
}, { timeout: operationTimeout }).catch((err) => {
|
|
546
|
-
if (transientDriverError(err)) return void 0;
|
|
547
|
-
throw err;
|
|
548
|
-
});
|
|
549
|
-
if (!sandbox) return { provisioning: true, retryAfterMs: 2e3 };
|
|
550
|
-
if (existing && plan) await applyBrokeredSecrets(existing, plan, input.workspaceId);
|
|
551
|
-
if (!await ensureStarted(sandbox)) return { provisioning: true, retryAfterMs: 2e3 };
|
|
552
|
-
if (existing && await applyNetworkPolicy(existing, input) === "retry") {
|
|
553
|
-
return { provisioning: true, retryAfterMs: 2e3 };
|
|
554
|
-
}
|
|
555
|
-
return readyTarget(input, sandbox, hostId);
|
|
556
|
-
}
|
|
557
|
-
return {
|
|
558
|
-
id: "daytona",
|
|
559
|
-
metadata: {
|
|
560
|
-
driverRunsIn: ["worker", "node"],
|
|
561
|
-
hostStopBehavior: "suspends-host",
|
|
562
|
-
hostResumeBehavior: "same-host",
|
|
563
|
-
targetAccess: "relay",
|
|
564
|
-
secretBrokering: "native",
|
|
565
|
-
egressControl: "hosts-and-cidrs",
|
|
566
|
-
persistence: sandboxDriverCatalog.daytona.metadata.persistence
|
|
567
|
-
},
|
|
568
|
-
ensureHost,
|
|
569
|
-
// Provider-state enumeration for `garbageCollect()`.
|
|
570
|
-
//
|
|
571
|
-
// Two label sets exist on a sandbox this driver created (`ensureHost`
|
|
572
|
-
// above): the manager's flat set — `app`, `workspaceId`, `epoch`,
|
|
573
|
-
// `homeRegion` (built at index.ts `ensureHostInput`) — and the dotted
|
|
574
|
-
// `claxedo.workspaceId` this driver adds for `findExisting`. Every target
|
|
575
|
-
// returned here carries `sandbox.labels` **as the provider reports them**,
|
|
576
|
-
// never labels reconstructed from ensure inputs: GC's ownership check reads
|
|
577
|
-
// `labels.app` and its identity check reads flat `labels.workspaceId` /
|
|
578
|
-
// `labels.epoch`, so a synthesized label set would decide the fate of a
|
|
579
|
-
// sandbox using values that never left this process. (That is the bug in
|
|
580
|
-
// the `exe` driver's `list()`, which sources labels from a per-process
|
|
581
|
-
// `Map` — after a restart it reports `labels: undefined` and GC skips
|
|
582
|
-
// every sandbox as unlabeled. Not fixed here; different owner.)
|
|
583
|
-
//
|
|
584
|
-
// The filter is deliberately WIDE: any claxedo ownership marker qualifies.
|
|
585
|
-
// Narrowing it here would re-create the defect W1 exists to remove — an
|
|
586
|
-
// orphan the sweep cannot see is an orphan that lives forever — and the
|
|
587
|
-
// authority on what may be DESTROYED is the manager's `app`-label check,
|
|
588
|
-
// which skips anything else as `unmanaged_app_label`. Visibility is this
|
|
589
|
-
// function's job; destruction is not.
|
|
590
|
-
//
|
|
591
|
-
// No preview URL is resolved: `getSignedPreviewUrl` is a per-sandbox
|
|
592
|
-
// round-trip and GC only needs identity, so a sweep over N orphans would
|
|
593
|
-
// cost N extra API calls to fill a field it then discards.
|
|
594
|
-
async list() {
|
|
595
|
-
const client = await resolveClient();
|
|
596
|
-
if (!client.list) {
|
|
597
|
-
throw new Error("daytona sandbox client does not support listing");
|
|
598
|
-
}
|
|
599
|
-
const targets = [];
|
|
600
|
-
const seen = /* @__PURE__ */ new Set();
|
|
601
|
-
for (let page = 1; page <= MAX_LIST_PAGES; page++) {
|
|
602
|
-
const result = await client.list(void 0, page, LIST_PAGE_SIZE);
|
|
603
|
-
const items = result?.items ?? [];
|
|
604
|
-
for (const sandbox of items) {
|
|
605
|
-
const labels = sandbox.labels;
|
|
606
|
-
const workspaceId = labels?.workspaceId ?? labels?.["claxedo.workspaceId"];
|
|
607
|
-
if (!workspaceId || seen.has(sandbox.id)) continue;
|
|
608
|
-
seen.add(sandbox.id);
|
|
609
|
-
targets.push({
|
|
610
|
-
workspaceId,
|
|
611
|
-
sandboxId: sandbox.id,
|
|
612
|
-
// Identity, not a reachable address — never routed through.
|
|
613
|
-
url: sandbox.id,
|
|
614
|
-
// Must equal what `ensureHost` stored on the lease, or a LIVE
|
|
615
|
-
// sandbox fails GC's identity check and gets destroyed. Both paths
|
|
616
|
-
// derive it from `labelName(workspaceId)`.
|
|
617
|
-
hostId: labelName(workspaceId),
|
|
618
|
-
driverResourceId: sandbox.id,
|
|
619
|
-
labels,
|
|
620
|
-
driver: { id: "daytona", resourceId: sandbox.id }
|
|
621
|
-
});
|
|
622
|
-
}
|
|
623
|
-
if (items.length < LIST_PAGE_SIZE) break;
|
|
624
|
-
}
|
|
625
|
-
return targets;
|
|
626
|
-
},
|
|
627
|
-
async resumeHost(input) {
|
|
628
|
-
const sandbox = await sandboxById(input.lease.sandboxId);
|
|
629
|
-
if (input.ensure.secrets !== void 0) {
|
|
630
|
-
const plan = await reconcileBrokeredSecrets(input.ensure, { withdraw: true, mountWithdrawn: true });
|
|
631
|
-
await applyBrokeredSecrets(sandbox, plan, input.ensure.workspaceId);
|
|
632
|
-
}
|
|
633
|
-
if (!await ensureStarted(sandbox)) return { provisioning: true, retryAfterMs: 2e3 };
|
|
634
|
-
if (await applyNetworkPolicy(sandbox, input.ensure) === "retry") {
|
|
635
|
-
return { provisioning: true, retryAfterMs: 2e3 };
|
|
636
|
-
}
|
|
637
|
-
return readyTarget(input.ensure, sandbox, input.ensure.hostId ?? labelName(input.ensure.workspaceId));
|
|
638
|
-
},
|
|
639
|
-
async touch(target) {
|
|
640
|
-
await sandboxById(target.sandboxId).then((sandbox) => sandbox.refreshActivity()).catch(() => void 0);
|
|
641
|
-
},
|
|
642
|
-
async suspend(target) {
|
|
643
|
-
await sandboxById(target.sandboxId).then((sandbox) => sandbox.stop(operationTimeout)).catch(() => void 0);
|
|
644
|
-
},
|
|
645
|
-
async stop(target) {
|
|
646
|
-
await sandboxById(target.sandboxId).then((sandbox) => sandbox.stop(operationTimeout)).catch(() => void 0);
|
|
647
|
-
},
|
|
648
|
-
async destroy(target) {
|
|
649
|
-
await sandboxById(target.sandboxId).then((sandbox) => sandbox.delete(operationTimeout)).catch((err) => {
|
|
650
|
-
if (driverErrorSignals(err).status !== 404) throw err;
|
|
651
|
-
});
|
|
652
|
-
if (!target.workspaceId) {
|
|
653
|
-
throw new Error(
|
|
654
|
-
`daytona deleted sandbox ${target.sandboxId} but cannot withdraw its brokered secrets: the destroy target names no workspace`
|
|
655
|
-
);
|
|
656
|
-
}
|
|
657
|
-
const secrets = await brokeredSecretService();
|
|
658
|
-
for (const secret of (await listWorkspaceSecrets(secrets, target.workspaceId)).values()) {
|
|
659
|
-
await withdrawSecret(secrets, secret, { delete: true });
|
|
660
|
-
}
|
|
661
|
-
},
|
|
662
|
-
async snapshot(target) {
|
|
663
|
-
const sandbox = await sandboxById(target.sandboxId);
|
|
664
|
-
if (!sandbox._experimental_createSnapshot) {
|
|
665
|
-
throw new Error("Daytona sandbox client does not support filesystem snapshots");
|
|
666
|
-
}
|
|
667
|
-
const snapshotId = `claxedo-${target.workspaceId ?? target.sandboxId}-${Date.now()}`;
|
|
668
|
-
await sandbox._experimental_createSnapshot(snapshotId, operationTimeout);
|
|
669
|
-
return { snapshotId };
|
|
670
|
-
}
|
|
671
|
-
};
|
|
672
|
-
}
|
|
673
|
-
function network(input) {
|
|
674
|
-
if (!input.net || input.net.mode === "allow-all") return {};
|
|
675
|
-
const hosts = input.net.hosts ?? [];
|
|
676
|
-
if (hosts.length > 0) return { domainAllowList: formatDaytonaDomainAllowList(hosts) };
|
|
677
|
-
const cidrs = input.net.cidrs ?? [];
|
|
678
|
-
if (cidrs.length === 0) return { networkBlockAll: true };
|
|
679
|
-
return { networkAllowList: formatDaytonaAllowList(cidrs) };
|
|
680
|
-
}
|
|
681
|
-
export {
|
|
682
|
-
createDaytonaSandboxDriver
|
|
683
|
-
};
|