@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/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
@@ -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,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC"}
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);