@mastra/e2b 0.0.0-studio-deploy-20260404182525 → 0.0.0-studio-cli-20260504022012
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 +99 -3
- package/README.md +7 -1
- package/dist/index.cjs +258 -2
- 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 +258 -2
- 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/s3.d.ts +5 -0
- package/dist/sandbox/mounts/s3.d.ts.map +1 -1
- package/dist/sandbox/mounts/types.d.ts +11 -2
- package/dist/sandbox/mounts/types.d.ts.map +1 -1
- package/package.json +13 -13
package/CHANGELOG.md
CHANGED
|
@@ -1,11 +1,107 @@
|
|
|
1
1
|
# @mastra/e2b
|
|
2
2
|
|
|
3
|
-
## 0.0.0-studio-
|
|
3
|
+
## 0.0.0-studio-cli-20260504022012
|
|
4
4
|
|
|
5
5
|
### Patch Changes
|
|
6
6
|
|
|
7
|
-
- Updated dependencies [[`
|
|
8
|
-
- @mastra/core@0.0.0-studio-
|
|
7
|
+
- Updated dependencies [[`6dcd65f`](https://github.com/mastra-ai/mastra/commit/6dcd65f2a34069e6dc43ba35f1d11119b9b40bef), [`c05c9a1`](https://github.com/mastra-ai/mastra/commit/c05c9a13230988cef6d438a62f37760f31927bc7), [`e24aacb`](https://github.com/mastra-ai/mastra/commit/e24aacba07bd66f5d95b636dc24016fca26b52cf), [`1c2dda8`](https://github.com/mastra-ai/mastra/commit/1c2dda805fbfccc0abf55d4cb20cc34402dc3f0c), [`c721164`](https://github.com/mastra-ai/mastra/commit/c7211643f7ac861f83b19a3757cc921487fc9d75), [`1b55954`](https://github.com/mastra-ai/mastra/commit/1b559541c1e08a10e49d01ffc51a634dfc37a286), [`5adc55e`](https://github.com/mastra-ai/mastra/commit/5adc55e63407be8ee977914957d68bcc2a075ceb), [`70017d7`](https://github.com/mastra-ai/mastra/commit/70017d72ab741b5d7040e2a15c251a317782e39e), [`e4942bc`](https://github.com/mastra-ai/mastra/commit/e4942bc7fdc903572f7d84f26d5e15f9d39c763d)]:
|
|
8
|
+
- @mastra/core@0.0.0-studio-cli-20260504022012
|
|
9
|
+
|
|
10
|
+
## 0.3.0
|
|
11
|
+
|
|
12
|
+
### Minor Changes
|
|
13
|
+
|
|
14
|
+
- Added Azure Blob sandbox mount support via blobfuse2 in @mastra/e2b and @mastra/daytona. `sandbox.mount(azureBlobFilesystem, '/data')` now works for Azure containers, matching the existing s3fs (S3) and gcsfuse (GCS) integration. Supports authentication via accountKey, sasToken, connectionString, or managed identity/default credentials, and preserves AzureBlobFilesystem prefixes when mounting. ([#15874](https://github.com/mastra-ai/mastra/pull/15874))
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
import { E2BSandbox } from '@mastra/e2b';
|
|
18
|
+
import { AzureBlobFilesystem } from '@mastra/azure/blob';
|
|
19
|
+
|
|
20
|
+
const azureFs = new AzureBlobFilesystem({ container: 'my-data', connectionString: '...' });
|
|
21
|
+
const sandbox = new E2BSandbox();
|
|
22
|
+
await sandbox.mount(azureFs, '/data');
|
|
23
|
+
// Sandbox processes can now read/write /data/* directly against the Azure container.
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Patch Changes
|
|
27
|
+
|
|
28
|
+
- Updated dependencies [[`6db978c`](https://github.com/mastra-ai/mastra/commit/6db978c42e94e75540a504f7230086f0b5cd35f9), [`512a013`](https://github.com/mastra-ai/mastra/commit/512a013f285aa9c0aa8f08a35b2ce09f9938b017), [`e9becde`](https://github.com/mastra-ai/mastra/commit/e9becdeed9176b9f8392e557bde12b933f99cf7a), [`703a443`](https://github.com/mastra-ai/mastra/commit/703a44390c587d9c0b8ae94ec4edd8afb2a74044), [`808df1b`](https://github.com/mastra-ai/mastra/commit/808df1b39358b5f10b7317107e42b1fda7c87185)]:
|
|
29
|
+
- @mastra/core@1.29.1
|
|
30
|
+
|
|
31
|
+
## 0.3.0-alpha.0
|
|
32
|
+
|
|
33
|
+
### Minor Changes
|
|
34
|
+
|
|
35
|
+
- Added Azure Blob sandbox mount support via blobfuse2 in @mastra/e2b and @mastra/daytona. `sandbox.mount(azureBlobFilesystem, '/data')` now works for Azure containers, matching the existing s3fs (S3) and gcsfuse (GCS) integration. Supports authentication via accountKey, sasToken, connectionString, or managed identity/default credentials, and preserves AzureBlobFilesystem prefixes when mounting. ([#15874](https://github.com/mastra-ai/mastra/pull/15874))
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
import { E2BSandbox } from '@mastra/e2b';
|
|
39
|
+
import { AzureBlobFilesystem } from '@mastra/azure/blob';
|
|
40
|
+
|
|
41
|
+
const azureFs = new AzureBlobFilesystem({ container: 'my-data', connectionString: '...' });
|
|
42
|
+
const sandbox = new E2BSandbox();
|
|
43
|
+
await sandbox.mount(azureFs, '/data');
|
|
44
|
+
// Sandbox processes can now read/write /data/* directly against the Azure container.
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Patch Changes
|
|
48
|
+
|
|
49
|
+
- Updated dependencies [[`512a013`](https://github.com/mastra-ai/mastra/commit/512a013f285aa9c0aa8f08a35b2ce09f9938b017), [`e9becde`](https://github.com/mastra-ai/mastra/commit/e9becdeed9176b9f8392e557bde12b933f99cf7a)]:
|
|
50
|
+
- @mastra/core@1.29.1-alpha.2
|
|
51
|
+
|
|
52
|
+
## 0.2.0
|
|
53
|
+
|
|
54
|
+
### Minor Changes
|
|
55
|
+
|
|
56
|
+
- Added S3 prefix (subdirectory) mount support. You can now mount a specific folder within an S3 bucket instead of the entire bucket by setting the `prefix` option on your S3 filesystem. ([#15171](https://github.com/mastra-ai/mastra/pull/15171))
|
|
57
|
+
|
|
58
|
+
**Example:**
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
const fs = new S3Filesystem({
|
|
62
|
+
bucket: 'my-bucket',
|
|
63
|
+
region: 'us-east-1',
|
|
64
|
+
prefix: 'workspace/data',
|
|
65
|
+
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
|
|
66
|
+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
|
|
67
|
+
});
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
When mounted in a sandbox, only the contents under `workspace/data/` in the bucket will be visible at the mount path. This uses the s3fs `bucket:/path` syntax under the hood.
|
|
71
|
+
|
|
72
|
+
Closes #15147.
|
|
73
|
+
|
|
74
|
+
### Patch Changes
|
|
75
|
+
|
|
76
|
+
- Updated dependencies [[`f112db1`](https://github.com/mastra-ai/mastra/commit/f112db179557ae9b5a0f1d25dc47f928d7d61cd9), [`21d9706`](https://github.com/mastra-ai/mastra/commit/21d970604d89eee970cbf8013d26d7551aff6ea5), [`0a0aa94`](https://github.com/mastra-ai/mastra/commit/0a0aa94729592e99885af2efb90c56aaada62247), [`ed07df3`](https://github.com/mastra-ai/mastra/commit/ed07df32a9d539c8261e892fc1bade783f5b41a6), [`01a7d51`](https://github.com/mastra-ai/mastra/commit/01a7d513493d21562f677f98550f7ceb165ba78c)]:
|
|
77
|
+
- @mastra/core@1.27.0
|
|
78
|
+
|
|
79
|
+
## 0.2.0-alpha.0
|
|
80
|
+
|
|
81
|
+
### Minor Changes
|
|
82
|
+
|
|
83
|
+
- Added S3 prefix (subdirectory) mount support. You can now mount a specific folder within an S3 bucket instead of the entire bucket by setting the `prefix` option on your S3 filesystem. ([#15171](https://github.com/mastra-ai/mastra/pull/15171))
|
|
84
|
+
|
|
85
|
+
**Example:**
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
const fs = new S3Filesystem({
|
|
89
|
+
bucket: 'my-bucket',
|
|
90
|
+
region: 'us-east-1',
|
|
91
|
+
prefix: 'workspace/data',
|
|
92
|
+
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
|
|
93
|
+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
|
|
94
|
+
});
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
When mounted in a sandbox, only the contents under `workspace/data/` in the bucket will be visible at the mount path. This uses the s3fs `bucket:/path` syntax under the hood.
|
|
98
|
+
|
|
99
|
+
Closes #15147.
|
|
100
|
+
|
|
101
|
+
### Patch Changes
|
|
102
|
+
|
|
103
|
+
- Updated dependencies [[`f112db1`](https://github.com/mastra-ai/mastra/commit/f112db179557ae9b5a0f1d25dc47f928d7d61cd9), [`21d9706`](https://github.com/mastra-ai/mastra/commit/21d970604d89eee970cbf8013d26d7551aff6ea5)]:
|
|
104
|
+
- @mastra/core@1.26.1-alpha.0
|
|
9
105
|
|
|
10
106
|
## 0.1.2
|
|
11
107
|
|
package/README.md
CHANGED
|
@@ -31,11 +31,12 @@ const agent = new Agent({
|
|
|
31
31
|
|
|
32
32
|
### Mounting Cloud Storage
|
|
33
33
|
|
|
34
|
-
E2B sandboxes can mount S3 or
|
|
34
|
+
E2B sandboxes can mount S3, GCS, or Azure Blob filesystems, making cloud storage accessible as a local directory inside the sandbox:
|
|
35
35
|
|
|
36
36
|
```typescript
|
|
37
37
|
import { Workspace } from '@mastra/core/workspace';
|
|
38
38
|
import { S3Filesystem } from '@mastra/s3';
|
|
39
|
+
import { AzureBlobFilesystem } from '@mastra/azure/blob';
|
|
39
40
|
import { E2BSandbox } from '@mastra/e2b';
|
|
40
41
|
|
|
41
42
|
const workspace = new Workspace({
|
|
@@ -46,6 +47,11 @@ const workspace = new Workspace({
|
|
|
46
47
|
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
|
|
47
48
|
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
|
|
48
49
|
}),
|
|
50
|
+
'/azure-data': new AzureBlobFilesystem({
|
|
51
|
+
container: 'my-container',
|
|
52
|
+
connectionString: process.env.AZURE_STORAGE_CONNECTION_STRING,
|
|
53
|
+
prefix: 'workspace/data',
|
|
54
|
+
}),
|
|
49
55
|
},
|
|
50
56
|
sandbox: new E2BSandbox(),
|
|
51
57
|
});
|
package/dist/index.cjs
CHANGED
|
@@ -35,6 +35,27 @@ function validateEndpoint(endpoint) {
|
|
|
35
35
|
throw new Error(`Invalid endpoint URL: "${endpoint}"`);
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
|
+
function validatePrefix(prefix) {
|
|
39
|
+
let normalized = prefix;
|
|
40
|
+
while (normalized.startsWith("/")) normalized = normalized.slice(1);
|
|
41
|
+
while (normalized.endsWith("/")) normalized = normalized.slice(0, -1);
|
|
42
|
+
if (!normalized) {
|
|
43
|
+
throw new Error("Mount prefix cannot be empty after normalization.");
|
|
44
|
+
}
|
|
45
|
+
if (normalized.includes("//") || normalized.split("/").some((s) => s === "." || s === "..")) {
|
|
46
|
+
throw new Error(`Invalid mount prefix: "${prefix}". Path traversal is not allowed.`);
|
|
47
|
+
}
|
|
48
|
+
if (/[\x00-\x1f\x7f]/.test(normalized)) {
|
|
49
|
+
throw new Error(`Invalid mount prefix: "${prefix}". Control characters are not allowed.`);
|
|
50
|
+
}
|
|
51
|
+
return normalized;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// src/utils/shell-quote.ts
|
|
55
|
+
function shellQuote(arg) {
|
|
56
|
+
if (/^[a-zA-Z0-9._\-/@:=]+$/.test(arg)) return arg;
|
|
57
|
+
return "'" + arg.replace(/'/g, "'\\''") + "'";
|
|
58
|
+
}
|
|
38
59
|
|
|
39
60
|
// src/sandbox/mounts/s3.ts
|
|
40
61
|
async function mountS3(mountPath, config, ctx) {
|
|
@@ -103,7 +124,12 @@ Error details: ${installResult.stderr || installResult.stdout}`
|
|
|
103
124
|
mountOptions.push("ro");
|
|
104
125
|
logger.debug(`${LOG_PREFIX} Mounting as read-only`);
|
|
105
126
|
}
|
|
106
|
-
|
|
127
|
+
let bucketArg = config.bucket;
|
|
128
|
+
if (config.prefix) {
|
|
129
|
+
const normalizedPrefix = validatePrefix(config.prefix);
|
|
130
|
+
bucketArg = `${config.bucket}:/${normalizedPrefix}`;
|
|
131
|
+
}
|
|
132
|
+
const mountCmd = `sudo s3fs ${shellQuote(bucketArg)} ${shellQuote(mountPath)} -o ${mountOptions.join(" -o ")}`;
|
|
107
133
|
logger.debug(`${LOG_PREFIX} Mounting S3:`, hasCredentials ? mountCmd.replace(credentialsPath, "***") : mountCmd);
|
|
108
134
|
try {
|
|
109
135
|
const result = await sandbox.commands.run(mountCmd, { timeoutMs: 6e4 });
|
|
@@ -171,6 +197,231 @@ async function mountGCS(mountPath, config, ctx) {
|
|
|
171
197
|
throw new Error(`Failed to mount GCS bucket: ${stderr || stdout || error}`);
|
|
172
198
|
}
|
|
173
199
|
}
|
|
200
|
+
var SAFE_CONTAINER_NAME = /^[a-z0-9][a-z0-9-]{1,61}[a-z0-9]$/;
|
|
201
|
+
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";
|
|
202
|
+
function validateContainerName(name) {
|
|
203
|
+
if (!SAFE_CONTAINER_NAME.test(name) || name.includes("--")) {
|
|
204
|
+
throw new Error(
|
|
205
|
+
`Invalid Azure container name: "${name}". Container names must be 3-63 lowercase alphanumeric characters or hyphens, with no consecutive hyphens.`
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
function parseConnectionString(cs) {
|
|
210
|
+
const out = {};
|
|
211
|
+
for (const part of cs.split(";")) {
|
|
212
|
+
const eq = part.indexOf("=");
|
|
213
|
+
if (eq === -1) continue;
|
|
214
|
+
const key = part.slice(0, eq).trim();
|
|
215
|
+
const value = part.slice(eq + 1).trim();
|
|
216
|
+
if (!value) continue;
|
|
217
|
+
if (key === "AccountName") out.accountName = value;
|
|
218
|
+
else if (key === "AccountKey") out.accountKey = value;
|
|
219
|
+
else if (key === "SharedAccessSignature") out.sasToken = value;
|
|
220
|
+
else if (key === "BlobEndpoint") out.endpoint = value;
|
|
221
|
+
else if (key === "EndpointSuffix") out.endpointSuffix = value;
|
|
222
|
+
else if (key === "DefaultEndpointsProtocol") out.protocol = value;
|
|
223
|
+
}
|
|
224
|
+
if (!out.endpoint && out.accountName) {
|
|
225
|
+
out.endpoint = `${out.protocol || "https"}://${out.accountName}.blob.${out.endpointSuffix || "core.windows.net"}`;
|
|
226
|
+
}
|
|
227
|
+
return out;
|
|
228
|
+
}
|
|
229
|
+
function yamlString(value) {
|
|
230
|
+
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
231
|
+
}
|
|
232
|
+
function parseOsRelease(output) {
|
|
233
|
+
const values = {};
|
|
234
|
+
for (const line of output.split("\n")) {
|
|
235
|
+
const eq = line.indexOf("=");
|
|
236
|
+
if (eq === -1) continue;
|
|
237
|
+
const key = line.slice(0, eq);
|
|
238
|
+
const value = line.slice(eq + 1).trim().replace(/^"|"$/g, "");
|
|
239
|
+
values[key] = value;
|
|
240
|
+
}
|
|
241
|
+
return values;
|
|
242
|
+
}
|
|
243
|
+
function resolveMicrosoftAptRepos(osReleaseOutput) {
|
|
244
|
+
const osRelease = parseOsRelease(osReleaseOutput);
|
|
245
|
+
const distroId = osRelease.ID || "ubuntu";
|
|
246
|
+
const codename = osRelease.VERSION_CODENAME || (distroId === "debian" ? "bookworm" : "jammy");
|
|
247
|
+
const versionId = osRelease.VERSION_ID || (distroId === "debian" ? "12" : "22.04");
|
|
248
|
+
if (!/^[a-z0-9][a-z0-9-]*$/.test(codename)) {
|
|
249
|
+
throw new Error(`Invalid distro codename for blobfuse2 repo: "${codename}"`);
|
|
250
|
+
}
|
|
251
|
+
if (!/^\d+(?:\.\d+)?$/.test(versionId)) {
|
|
252
|
+
throw new Error(`Invalid distro version for blobfuse2 repo: "${versionId}"`);
|
|
253
|
+
}
|
|
254
|
+
if (distroId === "debian") {
|
|
255
|
+
const repos = [
|
|
256
|
+
{ repoUrl: `https://packages.microsoft.com/debian/${versionId.split(".")[0]}/prod`, suite: codename }
|
|
257
|
+
];
|
|
258
|
+
if (versionId.split(".")[0] !== "12" || codename !== "bookworm") {
|
|
259
|
+
repos.push({ repoUrl: "https://packages.microsoft.com/debian/12/prod", suite: "bookworm" });
|
|
260
|
+
}
|
|
261
|
+
return repos;
|
|
262
|
+
}
|
|
263
|
+
if (distroId === "ubuntu") {
|
|
264
|
+
const repos = [{ repoUrl: `https://packages.microsoft.com/ubuntu/${versionId}/prod`, suite: codename }];
|
|
265
|
+
if (versionId !== "24.04" || codename !== "noble") {
|
|
266
|
+
repos.push({ repoUrl: "https://packages.microsoft.com/ubuntu/24.04/prod", suite: "noble" });
|
|
267
|
+
}
|
|
268
|
+
if (versionId !== "22.04" || codename !== "jammy") {
|
|
269
|
+
repos.push({ repoUrl: "https://packages.microsoft.com/ubuntu/22.04/prod", suite: "jammy" });
|
|
270
|
+
}
|
|
271
|
+
return repos;
|
|
272
|
+
}
|
|
273
|
+
throw new Error(`Unsupported distro for blobfuse2 runtime installation: "${distroId}"`);
|
|
274
|
+
}
|
|
275
|
+
function resolveAuth(config) {
|
|
276
|
+
let accountName = config.accountName;
|
|
277
|
+
let accountKey = config.accountKey;
|
|
278
|
+
let sasToken = config.sasToken;
|
|
279
|
+
let endpoint = config.endpoint;
|
|
280
|
+
if (config.connectionString) {
|
|
281
|
+
const parsed = parseConnectionString(config.connectionString);
|
|
282
|
+
accountName = accountName ?? parsed.accountName;
|
|
283
|
+
accountKey = accountKey ?? parsed.accountKey;
|
|
284
|
+
sasToken = sasToken ?? parsed.sasToken;
|
|
285
|
+
endpoint = endpoint ?? parsed.endpoint;
|
|
286
|
+
}
|
|
287
|
+
let mode;
|
|
288
|
+
if (config.useDefaultCredential) {
|
|
289
|
+
mode = "msi";
|
|
290
|
+
} else if (sasToken) {
|
|
291
|
+
mode = "sas";
|
|
292
|
+
} else if (accountKey) {
|
|
293
|
+
mode = "key";
|
|
294
|
+
} else {
|
|
295
|
+
throw new Error(
|
|
296
|
+
"Azure Blob mount requires credentials: provide connectionString, accountKey + accountName, sasToken + accountName, or useDefaultCredential."
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
if (!accountName) {
|
|
300
|
+
throw new Error("Azure Blob mount requires an accountName (either explicitly or via connectionString).");
|
|
301
|
+
}
|
|
302
|
+
if (endpoint) {
|
|
303
|
+
validateEndpoint(endpoint);
|
|
304
|
+
}
|
|
305
|
+
return { mode, accountName, accountKey, sasToken, endpoint };
|
|
306
|
+
}
|
|
307
|
+
function buildBlobfuseConfig(container, auth, cachePath, readOnly) {
|
|
308
|
+
const lines = [
|
|
309
|
+
"allow-other: true",
|
|
310
|
+
"foreground: false",
|
|
311
|
+
`read-only: ${readOnly ? "true" : "false"}`,
|
|
312
|
+
"logging:",
|
|
313
|
+
" type: silent",
|
|
314
|
+
"components:",
|
|
315
|
+
" - libfuse",
|
|
316
|
+
" - file_cache",
|
|
317
|
+
" - attr_cache",
|
|
318
|
+
" - azstorage",
|
|
319
|
+
"libfuse:",
|
|
320
|
+
" attribute-expiration-sec: 240",
|
|
321
|
+
" entry-expiration-sec: 240",
|
|
322
|
+
" negative-entry-expiration-sec: 120",
|
|
323
|
+
"file_cache:",
|
|
324
|
+
` path: ${yamlString(cachePath)}`,
|
|
325
|
+
" timeout-sec: 120",
|
|
326
|
+
"attr_cache:",
|
|
327
|
+
" timeout-sec: 7200",
|
|
328
|
+
"azstorage:",
|
|
329
|
+
` mode: ${auth.mode}`,
|
|
330
|
+
` account-name: ${yamlString(auth.accountName)}`,
|
|
331
|
+
` container: ${yamlString(container)}`
|
|
332
|
+
];
|
|
333
|
+
if (auth.mode === "key" && auth.accountKey) {
|
|
334
|
+
lines.push(` account-key: ${yamlString(auth.accountKey)}`);
|
|
335
|
+
} else if (auth.mode === "sas" && auth.sasToken) {
|
|
336
|
+
lines.push(` sas: ${yamlString(auth.sasToken)}`);
|
|
337
|
+
}
|
|
338
|
+
if (auth.endpoint) {
|
|
339
|
+
lines.push(` endpoint: ${yamlString(auth.endpoint.replace(/\/$/, ""))}`);
|
|
340
|
+
}
|
|
341
|
+
return lines.join("\n") + "\n";
|
|
342
|
+
}
|
|
343
|
+
async function mountAzure(mountPath, config, ctx) {
|
|
344
|
+
const { sandbox, logger } = ctx;
|
|
345
|
+
validateContainerName(config.container);
|
|
346
|
+
const auth = resolveAuth(config);
|
|
347
|
+
const prefix = config.prefix ? validatePrefix(config.prefix) : void 0;
|
|
348
|
+
const checkResult = await sandbox.commands.run('which blobfuse2 || echo "not found"');
|
|
349
|
+
if (checkResult.stdout.includes("not found")) {
|
|
350
|
+
logger.warn(`${LOG_PREFIX} blobfuse2 not found, attempting runtime installation...`);
|
|
351
|
+
logger.info(
|
|
352
|
+
`${LOG_PREFIX} Tip: For faster startup, pre-install blobfuse2 in your sandbox template via createMountableTemplate()`
|
|
353
|
+
);
|
|
354
|
+
const osReleaseResult = await sandbox.commands.run("cat /etc/os-release 2>/dev/null || true");
|
|
355
|
+
const repos = resolveMicrosoftAptRepos(osReleaseResult.stdout);
|
|
356
|
+
const repoSetupResult = await sandbox.commands.run(
|
|
357
|
+
"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",
|
|
358
|
+
{ timeoutMs: 6e4 }
|
|
359
|
+
);
|
|
360
|
+
let installResult;
|
|
361
|
+
if (repoSetupResult.exitCode === 0) {
|
|
362
|
+
for (const { repoUrl, suite } of repos) {
|
|
363
|
+
installResult = await sandbox.commands.run(
|
|
364
|
+
`echo "deb [signed-by=/etc/apt/keyrings/microsoft.gpg] ${repoUrl} ${suite} main" | sudo tee /etc/apt/sources.list.d/microsoft-prod.list && sudo apt-get update 2>&1 && sudo apt-get install -y blobfuse2 fuse3 2>&1`,
|
|
365
|
+
{ timeoutMs: 18e4 }
|
|
366
|
+
);
|
|
367
|
+
if (installResult.exitCode === 0) break;
|
|
368
|
+
logger.warn(`${LOG_PREFIX} blobfuse2 install failed for ${repoUrl} ${suite}, trying fallback if available`);
|
|
369
|
+
}
|
|
370
|
+
} else {
|
|
371
|
+
logger.warn(`${LOG_PREFIX} Failed to set up Microsoft apt repository, trying GitHub release fallback`);
|
|
372
|
+
}
|
|
373
|
+
let verifyResult = await sandbox.commands.run("which blobfuse2 && blobfuse2 --version", { timeoutMs: 3e4 });
|
|
374
|
+
if (verifyResult.exitCode !== 0) {
|
|
375
|
+
installResult = await sandbox.commands.run(
|
|
376
|
+
`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'`,
|
|
377
|
+
{ timeoutMs: 18e4 }
|
|
378
|
+
);
|
|
379
|
+
verifyResult = await sandbox.commands.run("which blobfuse2 && blobfuse2 --version", { timeoutMs: 3e4 });
|
|
380
|
+
}
|
|
381
|
+
if (!installResult || verifyResult.exitCode !== 0) {
|
|
382
|
+
throw new Error(
|
|
383
|
+
`Failed to install blobfuse2. For Azure Blob mounting, your template needs blobfuse2 and fuse3.
|
|
384
|
+
|
|
385
|
+
Option 1: Use createMountableTemplate() helper:
|
|
386
|
+
import { E2BSandbox, createMountableTemplate } from '@mastra/e2b';
|
|
387
|
+
const sandbox = new E2BSandbox({ template: createMountableTemplate() });
|
|
388
|
+
|
|
389
|
+
Option 2: Customize the base template:
|
|
390
|
+
new E2BSandbox({ template: base => base.aptInstall(['your-packages']) })
|
|
391
|
+
|
|
392
|
+
Error details: ${verifyResult.stderr || verifyResult.stdout || installResult?.stderr || installResult?.stdout || "unknown error"}`
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
const mountHash = crypto.createHash("md5").update(mountPath).digest("hex").slice(0, 8);
|
|
397
|
+
const configPath = `/tmp/.blobfuse2-config-${mountHash}.yaml`;
|
|
398
|
+
const cachePath = `/tmp/blobfuse2-cache-${mountHash}`;
|
|
399
|
+
const yaml = buildBlobfuseConfig(config.container, auth, cachePath, !!config.readOnly);
|
|
400
|
+
await sandbox.commands.run(`sudo rm -f ${configPath}`);
|
|
401
|
+
await sandbox.files.write(configPath, yaml);
|
|
402
|
+
await sandbox.commands.run(`sudo chown root:root ${configPath} && sudo chmod 600 ${configPath}`);
|
|
403
|
+
await sandbox.commands.run(`sudo rm -rf ${shellQuote(cachePath)} && sudo mkdir -p ${shellQuote(cachePath)}`);
|
|
404
|
+
const prefixFlags = prefix ? ` --virtual-directory=true --subdirectory=${shellQuote(prefix)}` : "";
|
|
405
|
+
const mountCmd = `sudo blobfuse2 mount ${shellQuote(mountPath)} --config-file=${shellQuote(configPath)}${prefixFlags}`;
|
|
406
|
+
logger.debug(`${LOG_PREFIX} Mounting Azure Blob:`, mountCmd);
|
|
407
|
+
try {
|
|
408
|
+
const result = await sandbox.commands.run(mountCmd, { timeoutMs: 6e4 });
|
|
409
|
+
logger.debug(`${LOG_PREFIX} blobfuse2 result:`, {
|
|
410
|
+
exitCode: result.exitCode,
|
|
411
|
+
stdout: result.stdout,
|
|
412
|
+
stderr: result.stderr
|
|
413
|
+
});
|
|
414
|
+
if (result.exitCode !== 0) {
|
|
415
|
+
throw new Error(`Failed to mount Azure Blob container: ${result.stderr || result.stdout}`);
|
|
416
|
+
}
|
|
417
|
+
} catch (error) {
|
|
418
|
+
const errorObj = error;
|
|
419
|
+
const stderr = errorObj.result?.stderr || "";
|
|
420
|
+
const stdout = errorObj.result?.stdout || "";
|
|
421
|
+
logger.error(`${LOG_PREFIX} blobfuse2 error:`, { stderr, stdout, error: String(error) });
|
|
422
|
+
throw new Error(`Failed to mount Azure Blob container: ${stderr || stdout || error}`);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
174
425
|
var E2BProcessHandle = class extends workspace.ProcessHandle {
|
|
175
426
|
pid;
|
|
176
427
|
_e2bHandle;
|
|
@@ -578,6 +829,11 @@ var E2BSandbox = class extends workspace.MastraSandbox {
|
|
|
578
829
|
await mountGCS(mountPath, config, mountCtx);
|
|
579
830
|
this.logger.debug(`${LOG_PREFIX} Mounted GCS bucket at ${mountPath}`);
|
|
580
831
|
break;
|
|
832
|
+
case "azure-blob":
|
|
833
|
+
this.logger.debug(`${LOG_PREFIX} Mounting Azure Blob container at ${mountPath}...`);
|
|
834
|
+
await mountAzure(mountPath, config, mountCtx);
|
|
835
|
+
this.logger.debug(`${LOG_PREFIX} Mounted Azure Blob container at ${mountPath}`);
|
|
836
|
+
break;
|
|
581
837
|
default:
|
|
582
838
|
this.mounts.set(mountPath, {
|
|
583
839
|
filesystem,
|
|
@@ -653,7 +909,7 @@ var E2BSandbox = class extends workspace.MastraSandbox {
|
|
|
653
909
|
}
|
|
654
910
|
this.logger.debug(`${LOG_PREFIX} Reconciling mounts. Expected paths:`, expectedMountPaths);
|
|
655
911
|
const mountsResult = await this._sandbox.commands.run(
|
|
656
|
-
`grep -E 'fuse\\.(s3fs|gcsfuse)' /proc/mounts | awk '{print $2}'`
|
|
912
|
+
`grep -E 'fuse\\.(s3fs|gcsfuse|blobfuse2)' /proc/mounts | awk '{print $2}'`
|
|
657
913
|
);
|
|
658
914
|
const currentMounts = mountsResult.stdout.trim().split("\n").filter((p) => p.length > 0);
|
|
659
915
|
this.logger.debug(`${LOG_PREFIX} Current FUSE mounts in sandbox:`, currentMounts);
|