@mastra/daytona 0.3.0 → 0.4.0-alpha.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/CHANGELOG.md +21 -0
- package/README.md +35 -5
- package/dist/index.cjs +250 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +250 -1
- package/dist/index.js.map +1 -1
- package/dist/sandbox/index.d.ts.map +1 -1
- package/dist/sandbox/mounts/azure.d.ts +40 -0
- package/dist/sandbox/mounts/azure.d.ts.map +1 -0
- package/dist/sandbox/mounts/index.d.ts +1 -0
- package/dist/sandbox/mounts/index.d.ts.map +1 -1
- package/dist/sandbox/mounts/types.d.ts +2 -1
- package/dist/sandbox/mounts/types.d.ts.map +1 -1
- package/package.json +11 -11
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { DaytonaSandbox, type DaytonaSandboxOptions } from './sandbox/index.js';
|
|
2
2
|
export { DaytonaProcessManager } from './sandbox/process-manager.js';
|
|
3
3
|
export { type DaytonaResources } from './sandbox/types.js';
|
|
4
|
-
export type { DaytonaMountConfig, DaytonaS3MountConfig, DaytonaGCSMountConfig } from './sandbox/mounts/index.js';
|
|
4
|
+
export type { DaytonaMountConfig, DaytonaS3MountConfig, DaytonaGCSMountConfig, DaytonaAzureBlobMountConfig, } from './sandbox/mounts/index.js';
|
|
5
5
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,KAAK,qBAAqB,EAAE,MAAM,WAAW,CAAC;AACvE,OAAO,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AAClE,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACxD,YAAY,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,KAAK,qBAAqB,EAAE,MAAM,WAAW,CAAC;AACvE,OAAO,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AAClE,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACxD,YAAY,EACV,kBAAkB,EAClB,oBAAoB,EACpB,qBAAqB,EACrB,2BAA2B,GAC5B,MAAM,kBAAkB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -266,6 +266,250 @@ Sandbox network response: ${checkOutput}` : "")
|
|
|
266
266
|
throw new Error(`Failed to mount GCS bucket: ${result.stderr || result.stdout}`);
|
|
267
267
|
}
|
|
268
268
|
}
|
|
269
|
+
var SAFE_CONTAINER_NAME = /^[a-z0-9][a-z0-9-]{1,61}[a-z0-9]$/;
|
|
270
|
+
var BLOBFUSE2_GITHUB_DEB = "https://github.com/Azure/azure-storage-fuse/releases/download/blobfuse2-2.5.1/blobfuse2-2.5.1-Ubuntu-22.04.x86_64.deb";
|
|
271
|
+
function validateContainerName(name) {
|
|
272
|
+
if (!SAFE_CONTAINER_NAME.test(name) || name.includes("--")) {
|
|
273
|
+
throw new Error(
|
|
274
|
+
`Invalid Azure container name: "${name}". Container names must be 3-63 lowercase alphanumeric characters or hyphens, with no consecutive hyphens.`
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
function parseConnectionString(cs) {
|
|
279
|
+
const out = {};
|
|
280
|
+
for (const part of cs.split(";")) {
|
|
281
|
+
const eq = part.indexOf("=");
|
|
282
|
+
if (eq === -1) continue;
|
|
283
|
+
const key = part.slice(0, eq).trim();
|
|
284
|
+
const value = part.slice(eq + 1).trim();
|
|
285
|
+
if (!value) continue;
|
|
286
|
+
if (key === "AccountName") out.accountName = value;
|
|
287
|
+
else if (key === "AccountKey") out.accountKey = value;
|
|
288
|
+
else if (key === "SharedAccessSignature") out.sasToken = value;
|
|
289
|
+
else if (key === "BlobEndpoint") out.endpoint = value;
|
|
290
|
+
else if (key === "EndpointSuffix") out.endpointSuffix = value;
|
|
291
|
+
else if (key === "DefaultEndpointsProtocol") out.protocol = value;
|
|
292
|
+
}
|
|
293
|
+
if (!out.endpoint && out.accountName) {
|
|
294
|
+
out.endpoint = `${out.protocol || "https"}://${out.accountName}.blob.${out.endpointSuffix || "core.windows.net"}`;
|
|
295
|
+
}
|
|
296
|
+
return out;
|
|
297
|
+
}
|
|
298
|
+
function yamlString(value) {
|
|
299
|
+
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
300
|
+
}
|
|
301
|
+
function parseOsRelease(output) {
|
|
302
|
+
const values = {};
|
|
303
|
+
for (const line of output.split("\n")) {
|
|
304
|
+
const eq = line.indexOf("=");
|
|
305
|
+
if (eq === -1) continue;
|
|
306
|
+
const key = line.slice(0, eq);
|
|
307
|
+
const value = line.slice(eq + 1).trim().replace(/^"|"$/g, "");
|
|
308
|
+
values[key] = value;
|
|
309
|
+
}
|
|
310
|
+
return values;
|
|
311
|
+
}
|
|
312
|
+
function resolveMicrosoftAptRepos(osReleaseOutput) {
|
|
313
|
+
const osRelease = parseOsRelease(osReleaseOutput);
|
|
314
|
+
const distroId = osRelease.ID || "ubuntu";
|
|
315
|
+
const codename = osRelease.VERSION_CODENAME || (distroId === "debian" ? "bookworm" : "jammy");
|
|
316
|
+
const versionId = osRelease.VERSION_ID || (distroId === "debian" ? "12" : "22.04");
|
|
317
|
+
if (!/^[a-z0-9][a-z0-9-]*$/.test(codename)) {
|
|
318
|
+
throw new Error(`Invalid distro codename for blobfuse2 repo: "${codename}"`);
|
|
319
|
+
}
|
|
320
|
+
if (!/^\d+(?:\.\d+)?$/.test(versionId)) {
|
|
321
|
+
throw new Error(`Invalid distro version for blobfuse2 repo: "${versionId}"`);
|
|
322
|
+
}
|
|
323
|
+
if (distroId === "debian") {
|
|
324
|
+
const repos = [
|
|
325
|
+
{ repoUrl: `https://packages.microsoft.com/debian/${versionId.split(".")[0]}/prod`, suite: codename }
|
|
326
|
+
];
|
|
327
|
+
if (versionId.split(".")[0] !== "12" || codename !== "bookworm") {
|
|
328
|
+
repos.push({ repoUrl: "https://packages.microsoft.com/debian/12/prod", suite: "bookworm" });
|
|
329
|
+
}
|
|
330
|
+
return repos;
|
|
331
|
+
}
|
|
332
|
+
if (distroId === "ubuntu") {
|
|
333
|
+
const repos = [{ repoUrl: `https://packages.microsoft.com/ubuntu/${versionId}/prod`, suite: codename }];
|
|
334
|
+
if (versionId !== "24.04" || codename !== "noble") {
|
|
335
|
+
repos.push({ repoUrl: "https://packages.microsoft.com/ubuntu/24.04/prod", suite: "noble" });
|
|
336
|
+
}
|
|
337
|
+
if (versionId !== "22.04" || codename !== "jammy") {
|
|
338
|
+
repos.push({ repoUrl: "https://packages.microsoft.com/ubuntu/22.04/prod", suite: "jammy" });
|
|
339
|
+
}
|
|
340
|
+
return repos;
|
|
341
|
+
}
|
|
342
|
+
throw new Error(`Unsupported distro for blobfuse2 runtime installation: "${distroId}"`);
|
|
343
|
+
}
|
|
344
|
+
function resolveAuth(config) {
|
|
345
|
+
let accountName = config.accountName;
|
|
346
|
+
let accountKey = config.accountKey;
|
|
347
|
+
let sasToken = config.sasToken;
|
|
348
|
+
let endpoint = config.endpoint;
|
|
349
|
+
if (config.connectionString) {
|
|
350
|
+
const parsed = parseConnectionString(config.connectionString);
|
|
351
|
+
accountName = accountName ?? parsed.accountName;
|
|
352
|
+
accountKey = accountKey ?? parsed.accountKey;
|
|
353
|
+
sasToken = sasToken ?? parsed.sasToken;
|
|
354
|
+
endpoint = endpoint ?? parsed.endpoint;
|
|
355
|
+
}
|
|
356
|
+
let mode;
|
|
357
|
+
if (config.useDefaultCredential) {
|
|
358
|
+
mode = "msi";
|
|
359
|
+
} else if (sasToken) {
|
|
360
|
+
mode = "sas";
|
|
361
|
+
} else if (accountKey) {
|
|
362
|
+
mode = "key";
|
|
363
|
+
} else {
|
|
364
|
+
throw new Error(
|
|
365
|
+
"Azure Blob mount requires credentials: provide connectionString, accountKey + accountName, sasToken + accountName, or useDefaultCredential."
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
if (!accountName) {
|
|
369
|
+
throw new Error("Azure Blob mount requires an accountName (either explicitly or via connectionString).");
|
|
370
|
+
}
|
|
371
|
+
if (endpoint) {
|
|
372
|
+
validateEndpoint(endpoint);
|
|
373
|
+
}
|
|
374
|
+
return { mode, accountName, accountKey, sasToken, endpoint };
|
|
375
|
+
}
|
|
376
|
+
function buildBlobfuseConfig(container, auth, cachePath, readOnly) {
|
|
377
|
+
const lines = [
|
|
378
|
+
"allow-other: true",
|
|
379
|
+
"foreground: false",
|
|
380
|
+
`read-only: ${readOnly ? "true" : "false"}`,
|
|
381
|
+
"logging:",
|
|
382
|
+
" type: silent",
|
|
383
|
+
"components:",
|
|
384
|
+
" - libfuse",
|
|
385
|
+
" - file_cache",
|
|
386
|
+
" - attr_cache",
|
|
387
|
+
" - azstorage",
|
|
388
|
+
"libfuse:",
|
|
389
|
+
" attribute-expiration-sec: 240",
|
|
390
|
+
" entry-expiration-sec: 240",
|
|
391
|
+
" negative-entry-expiration-sec: 120",
|
|
392
|
+
"file_cache:",
|
|
393
|
+
` path: ${yamlString(cachePath)}`,
|
|
394
|
+
" timeout-sec: 120",
|
|
395
|
+
"attr_cache:",
|
|
396
|
+
" timeout-sec: 7200",
|
|
397
|
+
"azstorage:",
|
|
398
|
+
` mode: ${auth.mode}`,
|
|
399
|
+
` account-name: ${yamlString(auth.accountName)}`,
|
|
400
|
+
` container: ${yamlString(container)}`
|
|
401
|
+
];
|
|
402
|
+
if (auth.mode === "key" && auth.accountKey) {
|
|
403
|
+
lines.push(` account-key: ${yamlString(auth.accountKey)}`);
|
|
404
|
+
} else if (auth.mode === "sas" && auth.sasToken) {
|
|
405
|
+
lines.push(` sas: ${yamlString(auth.sasToken)}`);
|
|
406
|
+
}
|
|
407
|
+
if (auth.endpoint) {
|
|
408
|
+
lines.push(` endpoint: ${yamlString(auth.endpoint.replace(/\/$/, ""))}`);
|
|
409
|
+
}
|
|
410
|
+
return lines.join("\n") + "\n";
|
|
411
|
+
}
|
|
412
|
+
async function mountAzure(mountPath, config, ctx) {
|
|
413
|
+
const { run, writeFile, logger } = ctx;
|
|
414
|
+
validateContainerName(config.container);
|
|
415
|
+
const auth = resolveAuth(config);
|
|
416
|
+
const prefix = config.prefix ? validatePrefix(config.prefix) : void 0;
|
|
417
|
+
const quotedMountPath = shellQuote(mountPath);
|
|
418
|
+
const curlCheck = await run('which curl 2>/dev/null || echo "not found"', 3e4);
|
|
419
|
+
if (curlCheck.stdout.includes("not found")) {
|
|
420
|
+
const curlInstall = await run("sudo apt-get update -qq 2>&1 && sudo apt-get install -y curl 2>&1", 12e4);
|
|
421
|
+
if (curlInstall.exitCode !== 0) {
|
|
422
|
+
throw new Error(
|
|
423
|
+
`Failed to install curl for Azure Blob reachability check: ${curlInstall.stderr || curlInstall.stdout}`
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
const probeUrl = auth.endpoint ? auth.endpoint.replace(/\/$/, "") : `https://${auth.accountName}.blob.core.windows.net`;
|
|
428
|
+
const connectivityCheck = await run(`curl -sS --max-time 5 ${shellQuote(probeUrl)} 2>&1`, 1e4);
|
|
429
|
+
const checkOutput = connectivityCheck.stdout.trim();
|
|
430
|
+
if (connectivityCheck.exitCode !== 0 || checkOutput.toLowerCase().includes("restricted") || checkOutput.toLowerCase().includes("blocked")) {
|
|
431
|
+
throw new Error(
|
|
432
|
+
`Cannot reach ${probeUrl} from this sandbox. Azure Blob mounting requires network access to the storage endpoint, which may be blocked on Daytona's restricted tiers. Upgrade to a tier with unrestricted internet access, or contact Daytona support to remove the network restriction.` + (checkOutput ? `
|
|
433
|
+
|
|
434
|
+
Sandbox network response: ${checkOutput}` : "")
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
const checkResult = await run('which blobfuse2 2>/dev/null || echo "not found"', 3e4);
|
|
438
|
+
if (checkResult.stdout.includes("not found")) {
|
|
439
|
+
logger.warn(`${LOG_PREFIX} blobfuse2 not found, attempting runtime installation...`);
|
|
440
|
+
logger.info(`${LOG_PREFIX} Tip: For faster startup, pre-install blobfuse2 in your sandbox image`);
|
|
441
|
+
await run("sudo apt-get update -qq 2>&1", 6e4);
|
|
442
|
+
const prepResult = await run("sudo apt-get install -y curl gnupg 2>&1", 12e4);
|
|
443
|
+
if (prepResult.exitCode !== 0) {
|
|
444
|
+
throw new Error(
|
|
445
|
+
`Failed to install blobfuse2 prerequisites (curl, gnupg): ${prepResult.stderr || prepResult.stdout}`
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
const osReleaseResult = await run("cat /etc/os-release 2>/dev/null || true", 3e4);
|
|
449
|
+
const repos = resolveMicrosoftAptRepos(osReleaseResult.stdout);
|
|
450
|
+
const repoSetup = await run(
|
|
451
|
+
"sudo mkdir -p /etc/apt/keyrings && curl --retry 3 --retry-all-errors --retry-delay 2 -fsSL https://packages.microsoft.com/keys/microsoft.asc -o /tmp/ms-key.asc && sudo gpg --batch --yes --dearmor -o /etc/apt/keyrings/microsoft.gpg /tmp/ms-key.asc",
|
|
452
|
+
3e4
|
|
453
|
+
);
|
|
454
|
+
let installResult;
|
|
455
|
+
if (repoSetup.exitCode === 0) {
|
|
456
|
+
for (const { repoUrl, suite } of repos) {
|
|
457
|
+
await run(
|
|
458
|
+
`echo "deb [signed-by=/etc/apt/keyrings/microsoft.gpg] ${repoUrl} ${suite} main" | sudo tee /etc/apt/sources.list.d/microsoft-prod.list`,
|
|
459
|
+
3e4
|
|
460
|
+
);
|
|
461
|
+
await run("sudo apt-get update -qq 2>&1 || true", 6e4);
|
|
462
|
+
installResult = await run("sudo apt-get install -y blobfuse2 fuse3 2>&1", 12e4);
|
|
463
|
+
if (installResult.exitCode === 0) break;
|
|
464
|
+
logger.warn(`${LOG_PREFIX} blobfuse2 install failed for ${repoUrl} ${suite}, trying fallback if available`);
|
|
465
|
+
}
|
|
466
|
+
} else {
|
|
467
|
+
logger.warn(`${LOG_PREFIX} Failed to set up Microsoft apt repository, trying GitHub release fallback`);
|
|
468
|
+
}
|
|
469
|
+
let verifyResult = await run("which blobfuse2 && blobfuse2 --version", 3e4);
|
|
470
|
+
if (verifyResult.exitCode !== 0) {
|
|
471
|
+
installResult = await run(
|
|
472
|
+
`sudo apt-get update -qq 2>&1 || true && sudo apt-get install -y fuse3 ca-certificates curl 2>&1 && curl -L --retry 3 --retry-all-errors --retry-delay 2 -fSLo /tmp/blobfuse2.deb ${BLOBFUSE2_GITHUB_DEB} && sudo dpkg -i /tmp/blobfuse2.deb 2>&1 && sudo bash -c 'lib=$(find /usr/lib -name "libfuse3.so.3.*" | head -1); [ -z "$lib" ] || ln -sf "$lib" /usr/lib/x86_64-linux-gnu/libfuse3.so.3'`,
|
|
473
|
+
18e4
|
|
474
|
+
);
|
|
475
|
+
verifyResult = await run("which blobfuse2 && blobfuse2 --version", 3e4);
|
|
476
|
+
}
|
|
477
|
+
if (!installResult || verifyResult.exitCode !== 0) {
|
|
478
|
+
throw new Error(
|
|
479
|
+
`Failed to install blobfuse2: ${verifyResult.stderr || verifyResult.stdout || installResult?.stderr || installResult?.stdout || "unknown error"}`
|
|
480
|
+
);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
const idResult = await run("id -u && id -g", 3e4);
|
|
484
|
+
const [uid, gid] = idResult.stdout.trim().split("\n");
|
|
485
|
+
const validUidGid = uid && gid && /^\d+$/.test(uid) && /^\d+$/.test(gid);
|
|
486
|
+
await run(
|
|
487
|
+
`sudo chmod a+rw /dev/fuse 2>/dev/null || true; sudo bash -c 'grep -q "^user_allow_other" /etc/fuse.conf 2>/dev/null || echo "user_allow_other" >> /etc/fuse.conf' 2>/dev/null || true`
|
|
488
|
+
);
|
|
489
|
+
const mountHash = createHash("md5").update(mountPath).digest("hex").slice(0, 8);
|
|
490
|
+
const configPath = `/tmp/.blobfuse2-config-${mountHash}.yaml`;
|
|
491
|
+
const cachePath = `/tmp/blobfuse2-cache-${mountHash}`;
|
|
492
|
+
const yaml = buildBlobfuseConfig(config.container, auth, cachePath, !!config.readOnly);
|
|
493
|
+
await run(`sudo rm -f ${shellQuote(configPath)}`, 3e4);
|
|
494
|
+
await writeFile(configPath, yaml);
|
|
495
|
+
await run(`chmod 600 ${shellQuote(configPath)}`, 3e4);
|
|
496
|
+
await run(`sudo rm -rf ${shellQuote(cachePath)} && mkdir -p ${shellQuote(cachePath)}`, 3e4);
|
|
497
|
+
if (validUidGid) {
|
|
498
|
+
await run(`sudo chown ${uid}:${gid} ${shellQuote(cachePath)} 2>/dev/null || true`, 3e4);
|
|
499
|
+
}
|
|
500
|
+
const prefixFlags = prefix ? ` --virtual-directory=true --subdirectory=${shellQuote(prefix)}` : "";
|
|
501
|
+
const mountCmd = `blobfuse2 mount ${quotedMountPath} --config-file=${shellQuote(configPath)}${prefixFlags}`;
|
|
502
|
+
logger.debug(`${LOG_PREFIX} Mounting Azure Blob:`, mountCmd);
|
|
503
|
+
const result = await run(mountCmd, 6e4);
|
|
504
|
+
logger.debug(`${LOG_PREFIX} blobfuse2 result:`, {
|
|
505
|
+
exitCode: result.exitCode,
|
|
506
|
+
stdout: result.stdout,
|
|
507
|
+
stderr: result.stderr
|
|
508
|
+
});
|
|
509
|
+
if (result.exitCode !== 0) {
|
|
510
|
+
throw new Error(`Failed to mount Azure Blob container: ${result.stderr || result.stdout}`);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
269
513
|
var DaytonaProcessHandle = class extends ProcessHandle {
|
|
270
514
|
pid;
|
|
271
515
|
_cmdId;
|
|
@@ -838,6 +1082,11 @@ var DaytonaSandbox = class _DaytonaSandbox extends MastraSandbox {
|
|
|
838
1082
|
await mountGCS(mountPath, config, mountCtx);
|
|
839
1083
|
this.logger.debug(`${LOG_PREFIX} Mounted GCS bucket at ${mountPath}`);
|
|
840
1084
|
break;
|
|
1085
|
+
case "azure-blob":
|
|
1086
|
+
this.logger.debug(`${LOG_PREFIX} Mounting Azure Blob at "${mountPath}"...`);
|
|
1087
|
+
await mountAzure(mountPath, config, mountCtx);
|
|
1088
|
+
this.logger.debug(`${LOG_PREFIX} Mounted Azure Blob container at ${mountPath}`);
|
|
1089
|
+
break;
|
|
841
1090
|
default: {
|
|
842
1091
|
const error = `Unsupported mount type: ${config.type}`;
|
|
843
1092
|
this.mounts.set(mountPath, { filesystem, state: "unsupported", config, error });
|
|
@@ -907,7 +1156,7 @@ var DaytonaSandbox = class _DaytonaSandbox extends MastraSandbox {
|
|
|
907
1156
|
try {
|
|
908
1157
|
const mountsResult = await runCommand(
|
|
909
1158
|
sandbox,
|
|
910
|
-
`grep -E 'fuse\\.(s3fs|gcsfuse)' /proc/mounts | awk '{print $2}'`,
|
|
1159
|
+
`grep -E 'fuse\\.(s3fs|gcsfuse|blobfuse2)' /proc/mounts | awk '{print $2}'`,
|
|
911
1160
|
{ timeout: MOUNT_COMMAND_TIMEOUT_MS }
|
|
912
1161
|
);
|
|
913
1162
|
currentMounts = mountsResult.output.trim().split("\n").filter((p) => p.length > 0);
|