-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Expand file tree
/
Copy pathr2.ts
More file actions
105 lines (95 loc) · 3.38 KB
/
Copy pathr2.ts
File metadata and controls
105 lines (95 loc) · 3.38 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
import type { Sandbox } from '@cloudflare/sandbox';
import type { MoltbotEnv } from '../types';
import { R2_MOUNT_PATH, getR2BucketName } from '../config';
/**
* Lock to prevent race conditions during mount operations
*/
let mountLock: Promise<void> | null = null;
/**
* Execute a function with a lock to prevent concurrent mount operations
*/
async function withMountLock<T>(fn: () => Promise<T>): Promise<T> {
// Wait for any existing lock to complete
while (mountLock) {
await mountLock;
}
// Create our lock
let releaseLock: () => void;
mountLock = new Promise((resolve) => {
releaseLock = resolve;
});
try {
return await fn();
} finally {
mountLock = null;
releaseLock!();
}
}
/**
* Check if R2 is already mounted by looking at the mount table
*/
async function isR2Mounted(sandbox: Sandbox): Promise<boolean> {
try {
const proc = await sandbox.startProcess(`mount | grep "s3fs on ${R2_MOUNT_PATH}"`);
// Wait for the command to complete
let attempts = 0;
while (proc.status === 'running' && attempts < 10) {
await new Promise(r => setTimeout(r, 200));
attempts++;
}
const logs = await proc.getLogs();
// If stdout has content, the mount exists
const mounted = !!(logs.stdout && logs.stdout.includes('s3fs'));
console.log('isR2Mounted check:', mounted, 'stdout:', logs.stdout?.slice(0, 100));
return mounted;
} catch (err) {
console.log('isR2Mounted error:', err);
return false;
}
}
/**
* Mount R2 bucket for persistent storage
*
* @param sandbox - The sandbox instance
* @param env - Worker environment bindings
* @returns true if mounted successfully, false otherwise
*/
export async function mountR2Storage(sandbox: Sandbox, env: MoltbotEnv): Promise<boolean> {
return withMountLock(async () => {
// Skip if R2 credentials are not configured
if (!env.R2_ACCESS_KEY_ID || !env.R2_SECRET_ACCESS_KEY || !env.CF_ACCOUNT_ID) {
console.log('R2 storage not configured (missing R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY, or CF_ACCOUNT_ID)');
return false;
}
// Check if already mounted first - this avoids errors and is faster
if (await isR2Mounted(sandbox)) {
console.log('R2 bucket already mounted at', R2_MOUNT_PATH);
return true;
}
const bucketName = getR2BucketName(env);
try {
console.log('Mounting R2 bucket', bucketName, 'at', R2_MOUNT_PATH);
await sandbox.mountBucket(bucketName, R2_MOUNT_PATH, {
endpoint: `https://${env.CF_ACCOUNT_ID}.r2.cloudflarestorage.com`,
// Pass credentials explicitly since we use R2_* naming instead of AWS_*
credentials: {
accessKeyId: env.R2_ACCESS_KEY_ID,
secretAccessKey: env.R2_SECRET_ACCESS_KEY,
},
});
console.log('R2 bucket mounted successfully - moltbot data will persist across sessions');
return true;
} catch (err) {
const errorMessage = err instanceof Error ? err.message : String(err);
console.log('R2 mount error:', errorMessage);
// Check again if it's mounted - the error might be misleading
if (await isR2Mounted(sandbox)) {
console.log('R2 bucket is mounted despite error');
return true;
}
// Don't fail if mounting fails - moltbot can still run without persistent storage
console.error('Failed to mount R2 bucket:', err);
return false;
}
});
}